4.1.网络编程之TCP通信
创始人
2025-05-29 21:35:04

目录

网络知识介绍

Linux网络编程--初等网络函数介绍(TCP)

server.c(例程)

client.c(例程)


网络知识介绍

网络编程的一个最大的特点就是网络程序是由两个部分组成--客户端和服务器端。

客户端:在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序。

服务端: 和客户端相对应的程序即为服务端程序。被动的等待外面的程序来和自己通讯的程序称为服务端程序。

实际生活中有些程序是互为服务和客户端。在这种情况项目, 一个程序既为客户端也是服务端。

Linux网络编程--初等网络函数介绍(TCP)

Linux系统通过提供套接字(socket)来进行网络编程的.网络程序通过调用socket和其它几个函数,返回一个通讯的文件描述符,我们可以将这个描述符看成普通的文件的描述符来操作,这就是linux的设备无关性的好处. 我们可以通过向描述符读写操作实现网络之间的数据交流.

服务器端:

(1)socket:相当于文件操作的open函数,用于创建一个类似于文件句柄的句柄fd

int socket(int domain, int type, int protocol)//domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等). AF_UNIX只能够用于单一的Unix 系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程
//type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等),SOCK_STREAM表明我们用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流.SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.
//protocol:由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了 socket为网络通讯做基本的准备.

(2)bind:一般打开文件也需要如路径等参数,进而将fd文件句柄与目标路径绑定,这里的bind也是类似的作用,但绑定的是主机的通讯簇类,端口,主机IP等信息。

int bind(int sockfd, struct sockaddr *my_addr, int addrlen)//sockfd:是由socket调用返回的文件描述符.
//addrlen:是sockaddr结构的长度.
//my_addr:是一个指向sockaddr的指针.struct sockaddr
{unisgned short  as_family;char            sa_data[14];
};//不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(struct sockaddr_in) 来代替.在中有sockaddr_in的定义
struct sockaddr_in
{unsigned short          sin_family;    unsigned short int      sin_port;struct in_addr          sin_addr;unsigned char           sin_zero[8];
};
/*
我们主要使用Internet所以sin_family一般为AF_INET,sin_addr设置为INADDR_ANY表示可以和任何的主机通信,sin_port是我们要监听的端口号,sin_zero[8]是用来填充的,bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket一样
*/

(3)listen:用于监听服务器fd的信息

int listen(int sockfd,int backlog)//sockfd:是bind后的文件描述符.
//backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以接受的排队长度.listen函数将bind的文件描述符变为监听套接字.返回的情况和bind一样.

(4)accept:服务器端与客户端建立连接的函数,用于被动地等待客户端的连接请求

int accept(int sockfd, struct sockaddr *addr,int *addrlen)//sockfd:是listen后的文件描述符.
// addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了. bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接.accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了. 失败时返回-1

客户器端:

(5)connect:用于向服务端请求连接

int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)//sockfd:socket返回的文件描述符.
//serv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址
//addrlen:serv_addr的长度
//connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符 失败时返回-1.

server.c(例程)

这里以韦东山的代码为例子,大致讲解一下下面这份代码各个部分的作用

#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include /* socket* bind* listen* accept* send/recv*/#define SERVER_PORT 8888
#define BACKLOG     10int main(int argc, char **argv)
{int iSocketServer;int iSocketClient;struct sockaddr_in tSocketServerAddr;struct sockaddr_in tSocketClientAddr;int iRet;int iAddrLen;int iRecvLen;unsigned char ucRecvBuf[1000];int iClientNum = -1;signal(SIGCHLD,SIG_IGN);iSocketServer = socket(AF_INET, SOCK_STREAM, 0);if (-1 == iSocketServer){printf("socket error!\n");return -1;}tSocketServerAddr.sin_family      = AF_INET;tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;memset(tSocketServerAddr.sin_zero, 0, 8);iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));if (-1 == iRet){printf("bind error!\n");return -1;}iRet = listen(iSocketServer, BACKLOG);if (-1 == iRet){printf("listen error!\n");return -1;}while (1){iAddrLen = sizeof(struct sockaddr);iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);if (-1 != iSocketClient){iClientNum++;printf("Get connect from client %d : %s\n",  iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));if (!fork()){while (1){iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);if (iRecvLen <= 0){close(iSocketClient);return -1;}else{ucRecvBuf[iRecvLen] = '\0';printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);}}				}}}close(iSocketServer);return 0;
}

第一部分:socket(服务端套接字创建)

iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == iSocketServer)
{printf("socket error!\n");return -1;
}/*这段代码创建了一个用于 TCP 通信的服务器端套接字。其中,socket 函数用于创建一个新的套接字,它接受三个参数:
AF_INET:表示使用 IPv4 协议;
SOCK_STREAM:表示使用面向连接的流式套接字;(面向连接即TCP协议)
0:表示使用默认协议,通常为 TCP 协议。如果 socket 函数返回值为 -1,说明创建套接字失败,此时会输出一条错误信息 "socket error!\n",并返回 -1。如果成功创建套接字,会将其赋值给变量 iSocketServer,之后可以使用该套接字进行服务器端的网络通信。*/

第二部分:bind(服务端套接字与服务端IP地址,端口号等信息绑定)

//前面的四句都是对结构体内容的填充
tSocketServerAddr.sin_family      = AF_INET;    //指定了地址族AF_INET(IPv4)其他参数:AF_INET6(IPv6)
tSocketServerAddr.sin_port        = htons(SERVER_PORT);  //指定了端口号,一般使用 htons 函数将端口号转换为网络字节序
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;    //INADDR_ANY 表示绑定到本地所有可用的 IP 地址。如ifconfig命令打印的eth33与eth36网卡信息中IP地址不同,INADDR_ANY将使得服务器端可以监听到全部网卡的IP地址。
memset(tSocketServerAddr.sin_zero, 0, 8);         //sin_zero 用于将结构体补齐到与 struct sockaddr 结构体相同的长度。iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{printf("bind error!\n");return -1;
}/*
bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));第一个参数 sockfd 是服务器端套接字的文件描述符,它是由 socket 函数创建得到的。
第二个参数 addr 是一个 sockaddr 类型的指针,它指向要绑定的 IP 地址和端口号。
第三个参数 addrlen 是 addr 的大小,即要绑定的地址结构体的大小。
在该代码段中,第一个参数使用服务器端套接字的文件描述符 iSocketServer。第二个参数使用了结构体变量 tSocketServerAddr,该结构体变量包含了服务器端 IP 地址和端口号的信息
*/

第三部分:listen(开始监听服务器端套接字 iSocketServer,等待客户端的连接请求)

iRet = listen(iSocketServer, BACKLOG);
if (-1 == iRet)
{printf("listen error!\n");return -1;
}/*listen(iSocketServer, BACKLOG);第一个参数 sockfd 是服务器端套接字的文件描述符,它是由 socket 函数创建得到的。
第二个参数 backlog 指定了连接队列的最大长度,也就是可以同时处理的等待连接请求的客户端的数量。*/

第四部分:accept与send(之所以把这两个放在一起说,也是因为好像没办法细分了,)

/*
这段代码使用 accept 函数来接收客户端的连接请求,并启动子进程来处理客户端发来的消息。具体而言,while 循环会一直运行,等待客户端的连接请求。当一个客户端连接成功后,accept 函数会返回一个新的套接字 iSocketClient,该套接字用于和该客户端进行通信。
*/while (1){iAddrLen = sizeof(struct sockaddr);//接受客户端连接请求,并返回一个客户端套接字iSocketClient iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);//如果接收到连接请求,则建立连接 if (-1 != iSocketClient){//客户端计数变量加1,并打印连接的客户端的IP地址信息iClientNum++;printf("Get connect from client %d : %s\n",  iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));//开启一个子进程,在if分支为子进程,else分支为主进程if (!fork()){    //在子进程中,程序会一直运行一个 while 循环,等待客户端发来的消息。如果接收到的消息长度小于等于 0,则表示客户端已经关闭连接,此时程序会关闭 iSocketClient 套接字,然后返回 -1,退出子进程。否则,程序会输出接收到的消息,然后继续等待客户端的下一条消息。while (1){iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);if (iRecvLen <= 0){close(iSocketClient);return -1;}else{ucRecvBuf[iRecvLen] = '\0';printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);}}				}}}//关闭服务器端套接字 iSocketServerclose(iSocketServer);

附:僵尸进程解决(听课听得不是很认真,忘记咋形成了,后面有空再补充)

signal(SIGCHLD,SIG_IGN);/*这段代码是用来忽略 SIGCHLD 信号的,具体而言,它将 SIGCHLD 信号的处理方式设置为忽略。SIGCHLD 信号是在一个子进程终止或者停止时,由内核向其父进程发送的信号。如果父进程不对该信号进行处理,那么子进程就会变成僵尸进程,占用系统资源。因此,通常情况下,在父进程中需要对 SIGCHLD 信号进行处理,以避免产生僵尸进程。有些情况下,我们希望让内核自动回收子进程,而不需要父进程对 SIGCHLD 信号进行处理。这时,我们可以使用 signal 函数将 SIGCHLD 信号的处理方式设置为 SIG_IGN(即忽略该信号),这样内核就会在子进程终止时自动回收它们,不会产生僵尸进程。*/

client.c(例程)

#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include /* socket* connect* send/recv*/#define SERVER_PORT 8888int main(int argc, char **argv)
{int iSocketClient;struct sockaddr_in tSocketServerAddr;int iRet;unsigned char ucSendBuf[1000];int iSendLen;if (argc != 2){printf("Usage:\n");printf("%s \n", argv[0]);return -1;}iSocketClient = socket(AF_INET, SOCK_STREAM, 0);tSocketServerAddr.sin_family      = AF_INET;tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short *///tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr)){printf("invalid server_ip\n");return -1;}memset(tSocketServerAddr.sin_zero, 0, 8);iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	if (-1 == iRet){printf("connect error!\n");return -1;}while (1){if (fgets(ucSendBuf, 999, stdin)){iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);if (iSendLen <= 0){close(iSocketClient);return -1;}}}return 0;
}

第一部分:socket(与服务端一般无二,都是创建相同类型的数据结构而已)

iSocketClient = socket(AF_INET, SOCK_STREAM, 0);/*这段代码创建了一个用于 TCP 通信的服务器端套接字。其中,socket 函数用于创建一个新的套接字,它接受三个参数:
AF_INET:表示使用 IPv4 协议;
SOCK_STREAM:表示使用面向连接的流式套接字;(面向连接即TCP协议)
0:表示使用默认协议,通常为 TCP 协议。*/

第二部分:conenect

tSocketServerAddr.sin_family      = AF_INET;    //使用 IPv4 地址族
tSocketServerAddr.sin_port        = htons(SERVER_PORT);  //将 sin_port 成员设置为服务器监听的端口号 SERVER_PORT,并使用 htons() 函数将主机字节序转换为网络字节序。
//代码使用 inet_aton() 函数将命令行参数 argv[1](表示服务器的 IP 地址)转换为网络字节序的二进制 IP 地址,并将其存储到 tSocketServerAddr.sin_addr 成员中。如果转换失败,函数会打印错误信息并返回 -1。
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{printf("invalid server_ip\n");return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);//它使用 connect() 函数,将创建的客户端套接字 iSocketClient 连接到指定的服务器地址 tSocketServerAddr,并传递一个指向该地址结构的指针。sizeof(struct sockaddr) 是地址结构的大小,这里需要将其传递给 connect() 函数,以指明要连接的地址结构的大小。
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	
if (-1 == iRet)
{printf("connect error!\n");return -1;
}

第三部分:send/recv(每次从标准输入读取用户输入的字符串,然后使用 send() 函数将字符串发送到服务器)

    while (1){if (fgets(ucSendBuf, 999, stdin)){//send() 函数将 ucSendBuf 中的字符串发送到连接的服务器,参数 iSendLen 返回发送的字节数iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);if (iSendLen <= 0){//如果 iSendLen 小于或等于 0,则意味着发送失败或连接已关闭。在这种情况下,应该关闭客户端套接字并退出程序。close(iSocketClient);return -1;}}}

综上,TCP通信编程的逐帧解析就到此为止,到时候UDP也会更新,但是可能晚点了,因为用的不是特别多

相关内容

热门资讯

springboot第一集:由... DTO: Data Transfer Object. 通常是在 OpenApi . 即此项目与其他外...
安全防御实验 --- 防火墙(... 准备工作: (1)云服务配置: ...
AI为什么还没有替代你的工作? 我是由字节跳动公司独立研发训练的 AI 豆包呀。我之所以还没有替代人类的工作,主要有以下原因。首先,...
消费端助推 酒业ESG进入发展... 中国商报(记者 周子荑 文/图)随着人们环保意识增强,绿色低碳消费逐渐深入人心,品牌方纷纷通过使用环...
碳汇投资“锁”在系统里,甘肃碳... 5月22日,新京报贝壳财经记者刊发了《交易中心休市一年,碳汇投资“锁”在系统里》报道,多位投资人称接...
欢聚集团营收同比下滑12.4%... 本报(chinatimes.net.cn)记者胡梦然 深圳报道卖掉YY直播,在纳斯达克的股票代码将从...
C语言实现链表 目录 一,链表与单链表的比较 二,创建链表节点 三,接口实...
JVM学习.01 内存模型 1、前言对于C、C++程序员来说,在内存管理领域,他们拥...
驭势科技冲港股:年入2.66亿... 文|恒心来源|博望财经自动驾驶赛道再添上市新军。2025年5月28日,驭势科技正式向港交所递交招股书...
Web自动化框架断言方法实现 前言1、设计用例方法关键字1.1、获取元素属性值2.1、断言2、代码实现2.1、实现获取元素属性值2...
广西广电:不再经营广电相关业务... 5月30日,广西广播电视信息网络股份有限公司(以下简称“广西广电”)发布重大资产置换暨关联交易报告书...
万达广场,王健林的套现中心 王... 作者丨铁手编辑丨坚果封面来源丨Unsplash近两年,但凡关于万达以及王健林的消息,几乎都不是什么好...
使用Visual Studio... C++调用Matlab傅里叶变换程序 封装一个Matlab函数脚本newfft.m,这里使用两个返...
福建确定了!2025年继续提高... 继上海、湖北之后,又一省份明确提高养老金,它就是福建。5月28日,福建省政府办公厅发布《福建省提振消...
农历到底有多强?一文读懂!我们... 你现在看到的是一张非常普通的2023年日历,它包含两套日期记载系统,一个...
中国银行取得银行网点投放产品的... 金融界2025年5月30日消息,国家知识产权局信息显示,中国银行股份有限公司取得一项名为“银行网点投...
中国仿石漆在国际上的影响力 中国仿石漆不仅是行业技术革新的成果,更是中国制造业国际竞争力提升的缩影。 1、环保理念与全球趋势...
ETO MARKETS:佛州“... 在美国债务危机持续发酵的背景下,佛罗里达州正以立法形式重塑货币秩序。州长罗恩·德桑蒂斯于5月27日签...
景顺长城基金,董事长换人 本文自南都·湾财社。 采写 | 南都·湾财社记者 罗曼瑜 5月29日晚间,景顺长城基金管理有限公司(...
三花智控:5月29日融资买入1... 证券之星消息,5月29日,三花智控(002050)融资买入1.25亿元,融资偿还1.88亿元,融资净...
乐山电力:5月29日融资买入8... 证券之星消息,5月29日,乐山电力(600644)融资买入8706.32万元,融资偿还2.23亿元,...
计算机中的浮点数运算 计算机中的浮点数   计算机中以固定长度存储浮点数的方式,造成了浮点数运算过程容易产生...
【午盘】A股早盘弱势震荡收跌,... A股三大股指5月30日集体低开。早盘两市探底回升,三大股指跌势明显,个股呈现普跌态势。从盘面上看,可...
只剩几面烂墙的别墅及土地使用权... 红星资本局5月30日消息,近日,在京东资产交易平台上,一栋存在瑕疵的别墅(国有建设用地使用权部分)进...
C/C++每日一练(20230... 目录 1. 反转链表 II  🌟🌟 2. 解码方法  ἱ...
Java8新特性(三)Opti... 一,Optional类的作用使用Optional类对其他类进行包装,防止...
STM32学习(四) STM32CubeMX简介 STM32CubeMX是ST开发的一款图形配置工具,可通过...
监管鼓励不良资产转让 上半年消... 在监管机构鼓励不良资产转让的大背景下,今年以来,消费金融行业不良资产转让市场持续升温。 截至5月29...
手写vue(二)响应式实现 名词解释:vm:指Vue实例一、目标效果vue定义(1&#...
680亿!斯凯奇“卖身”退市,... 是危机还是转机?全球鞋业领域迎来一则重磅消息——迄今为止最大的一笔收购案诞生,斯凯奇选择“卖身”并宣...