当前位置: 代码迷 >> 综合 >> Linux下C语言编程(4):TCP/IP 网络通信
  详细解决方案

Linux下C语言编程(4):TCP/IP 网络通信

热度:104   发布时间:2023-09-22 08:17:55.0
笔者今天来讲讲Linux下Socket通信,包括客户端和服务端。

客户端

客户端连接服务器的思路是:

1.建立Socket链路,
2.设置通信方式,服务器的IP和端口,具体设置 sockaddr_in这个结构体参数
3.连接服务器,连接成功就可以收发数据。
4.正常收发数据
5.关闭socket通信


有了思路,就可以上手写代码了,哈哈加油!

建立socket链接使用 socket(int af, int type, int protocol);这个函数。

参数1:af表示IP类型AF_INET 表示IPV4类型,如果要使用AF_INET6 则表示IPV6的类型

参数2:type表示套接字类型SOCK_STREAM,表示面向链接的套接字(即TCP),如果是SOCK_DGRAM,则表示面向无连接(数据报)的套接字(即UDP)。

参数3:protocol,表示TCP通信还是UDP通信,一般前面套接字类型就可以决定通信方式,最后一个参数一般设为0

设置sockaddr_in结构体,

struct sockaddr_in
{
    short sin_family;            //地址类型unsigned short sin_port;     //连接端口,需要htons转换成网络数据格式struct in_addr sin_addr;     //IP地址,也是网络地址格式unsigned char sin_zero[8];   //*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/}struct sockaddr_in ServerAddr;
struct hostent *Host;
Host = gethostbyname(ServerIP);
Sockfd = socket(AF_INET,SOCK_STREAM,0);ServerAddr.sin_family  = AF_INET;
ServerAddr.sin_port    = htons(ServerPort);
ServerAddr.sin_addr    = *((struct in_addr *)Host->h_addr);         //返回一个网络数据格式的IP地址。
数据的发送

数据的发送比较简单,直接调用send函数即可。

int send( SOCKET s, const char FAR buf, int len, int flags );

参数1:socket通信链路标示符
参数2:发送缓冲区
参数3:发送的字节数
参数4:一般设置为0

send(Sockfd,SendBuff,ByteNum,0);

如果返回值为-1,则说明通信链路有问题,需要关闭连接(close(标示符);),重新进行连接。```

数据的接收

数据的接收同样和上篇博文串口数据接收一样,需要调用select函数来监测socket标示符是否变化即是否接收到数据

相当于触发接收之后,可以调用 recv函数接收到数据

int len = recv(Sockfd, RecBuff, MaxLength, 0);

参数1:socket标示符
参数2:接收缓存区
参数3:接收最大的字符长度
参数4:一般设置为0

fd_set rfds_Socket;
FD_ZERO(&rfds_Socket);
FD_SET(Sockfd, &rfds_Socket);
int retval=select(Sockfd + 1, &rfds_Socket, NULL, NULL, NULL);//tv控制选择的时间,若规定时间内没有收到数据//则不监控该rfds。则若为NULL,一直等到接收到数据//再继续程序执行。
printf("retval=%d--rfds_Socket=%d\n",rfds_Socket);
if(retval==0)
{
    printf("select error\n");continue;
}
while(Sockfd!=-1)
{
    if(FD_ISSET(Sockfd, &rfds_Socket)) //接收到数据了{
    bzero(RecBuff, MaxLength);int  len = recv(Sockfd, RecBuff, MaxLength, 0);strcpy(SendBuff,RecBuff);ByteNum=len;memset(RecBuff,0x00,sizeof(RecBuff));}}

完整的发送和接收函数代码如下:调用两个线程来进行处理。

做法是将服务器收到的数据,再发送回去。

void *pthread_NetRecFun(void *arg)
{
    char ServerIP[50];char Port[50];while(*(int*)arg){
    if(Sockfd==-1){
    ConfigGetKey("NetConfig.dat", "net:server", "ServerIP", ServerIP);ConfigGetKey("NetConfig.dat", "net:server", "ServerPort", Port);printf("%s---",ServerIP);printf("%s\n",Port);//char *ServerIP = "192.168.0.100";int ServerPort = atoi(Port);struct sockaddr_in ServerAddr;struct hostent *Host;Host = gethostbyname(ServerIP);Sockfd = socket(AF_INET,SOCK_STREAM,0);if(Sockfd == -1){
    printf("Socket established failed!\n");sleep(2);continue;}bzero(&ServerAddr,sizeof(ServerAddr));ServerAddr.sin_family = AF_INET;ServerAddr.sin_port = htons(ServerPort);ServerAddr.sin_addr = *((struct in_addr *)Host->h_addr);if(connect(Sockfd,(struct sockaddr*)&ServerAddr,sizeof(struct sockaddr))==-1){
    close(Sockfd);Sockfd=-1;printf("Socket connected failed!\n");sleep(2);continue;}}printf("connect Successfully*******SocketRec_VST=%d\n",Sockfd);fd_set rfds_Socket;FD_ZERO(&rfds_Socket);FD_SET(Sockfd, &rfds_Socket);int retval=select(Sockfd + 1, &rfds_Socket, NULL, NULL, NULL);//tv控制选择的时间,若规定时间内没有收到数据//则不监控该rfds。则若为NULL,一直等到接收到数据//再继续程序执行。printf("retval=%d--rfds_Socket=%d\n",rfds_Socket);if(retval==0){
    printf("select error\n");continue;}while(Sockfd!=-1){
    if(FD_ISSET(Sockfd, &rfds_Socket)) //接收到数据了{
    bzero(RecBuff, MaxLength);int  len = recv(Sockfd, RecBuff, MaxLength, 0);strcpy(SendBuff,RecBuff);ByteNum=len;memset(RecBuff,0x00,sizeof(RecBuff));}}}
}
void *pthread_NetSendFun(void *arg)
{
    char ServerIP[50];char Port[50];while(*(int*)arg){
    if(Sockfd==-1){
    ConfigGetKey("NetConfig.dat", "net:server", "ServerIP", ServerIP);ConfigGetKey("NetConfig.dat", "net:server", "ServerPort", Port);printf("%s---",ServerIP);printf("%s\n",Port);//char *ServerIP = "192.168.0.100";int ServerPort = atoi(Port);struct sockaddr_in ServerAddr;struct hostent *Host;Host = gethostbyname(ServerIP);Sockfd = socket(AF_INET,SOCK_STREAM,0);if(Sockfd == -1){
    printf("Socket established failed!\n");sleep(2);continue;}bzero(&ServerAddr,sizeof(ServerAddr));ServerAddr.sin_family = AF_INET;ServerAddr.sin_port = htons(ServerPort);ServerAddr.sin_addr = *((struct in_addr *)Host->h_addr);if(connect(Sockfd,(struct sockaddr*)&ServerAddr,sizeof(struct sockaddr))==-1){
    close(Sockfd);Sockfd=-1;printf("Socket connected failed!\n");sleep(2);continue;}}if(strlen(SendBuff)>0){
    //在linux下写socket的程序的时候,如果通信链接断开之后在调用发送函数,就会让底层抛出一个SIGPIPE信号。这个信号的缺省处理方法是退出进程,但是这都不是我们期望的。因此我们需要重载这个信号的处理方法。调用以下代码,即可安全的屏蔽SIGPIPE:signal(SIGPIPE, SIG_IGN);  int len = send(Sockfd,SendBuff,ByteNum,0);printf("len=%d---",len);if(len==-1){
    printf("Failed to Send Message!\n");close(Sockfd);Sockfd=-1;continue;}printf("NET:%s--%d---%d\n",SendBuff,ByteNum,strlen(SendBuff));memset(SendBuff,0x00,sizeof(SendBuff));ByteNum=0;}}
}

网络助手作为服务器,本例(Beagle Bone)作为客户端,连接成功之后,Beagle Bone将收到网络助手的数据再转发回去。

Beagle Bone IP:192.168.0.152
网络助手服务器IP:192.168.0.100 端口:51230,在同一个局域网段。
Linux下C语言编程(4):TCP/IP 网络通信
Linux下C语言编程(4):TCP/IP 网络通信

服务端

服务端监听客户端连接的思路如下:

1.建立socket通信链路,确定通信方式(TCP/UDP)和通信地址(IPV4或者IPV6)
2.设置服务器的IP和端口,具体设置 sockaddr_in这个结构体参数
3.将套接字绑定到本地地址和端口上面
4,监听套接字,保存用户的请求连接信息。
5.正常收发数据。
6.关闭socket通信

建立socket通信链路,设置IP和端口,和客户端有区别的就是地址设置为任意即可。其他参数和顺序都一样。

serveraddr.sin_addr.s_addr = INADDR_ANY;

listenfd = socket(AF_INET, SOCK_STREAM, 0);
printf("listenfd=%d\n", listenfd);//定义一个结构体变量servaddr,用来记录给定的IP和port信息,为bind函数做准备
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT); //把端口转化为网络字节序,即大端模式
serveraddr.sin_addr.s_addr = INADDR_ANY;
绑定套接字

参数1;socket通信标示符
参数2:服务器地址信息,包括IP和端口
参数3:数据长度

//把“本地含义的描述符”绑定到一个IP和Port上,此时这个socket才具备对外连接的能力
bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
监听套接字

参数1;socket通信标示符
参数2:指定了监听连接的最大个数

//创建一个监听队列,用来保存用户的请求连接信息(ip、port、protocol)
listen(listenfd, BACKLOG);
等待接收客户端连接,

该函数为阻塞式,等到有客户端连接则执行完该函数。
参数1;socket通信标示符
参数2:客户端地址信息,包括IP和端口
参数3:接收长度

connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peer_len);

连接完成之后,则可以正常和客户端进行通信。
完整的代码如下:

int ServerConnectSocket()
{
    int listenfd;//创建一个socket描述符,此描述符仅是本主机上的一个普通文件描述符而已listenfd = socket(AF_INET, SOCK_STREAM, 0);printf("listenfd=%d\n", listenfd);//定义一个结构体变量servaddr,用来记录给定的IP和port信息,为bind函数做准备struct sockaddr_in serveraddr;bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(PORT); //把端口转化为网络字节序,即大端模式serveraddr.sin_addr.s_addr = INADDR_ANY;//把“本地含义的描述符”绑定到一个IP和Port上,此时这个socket才具备对外连接的能力bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));//创建一个监听队列,用来保存用户的请求连接信息(ip、port、protocol)listen(listenfd, BACKLOG);printf("======bind success,waiting for client's request!!!======\n");//让操作系统回填client的连接信息(ip、port、protocol)return listenfd;
}
int main()
{
    TimerFunc();int connfd;int listenfd = ServerConnectSocket();while(1){
    //accept函数从listen函数维护的监听队列里取一个客户连接请求处理struct sockaddr_in peeraddr;socklen_t peer_len = sizeof(peeraddr);printf("Port=%d\r\n",PORT);connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peer_len);printf("=====================Client Connect Successfully=====================\n");printf("IP = %s:PORT = %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));char buf[MAXDATASIZE];fd_set rfds_Socket;FD_ZERO(&rfds_Socket);FD_SET(connfd, &rfds_Socket);int retval=select(connfd + 1, &rfds_Socket, NULL, NULL, NULL);//tv控制选择的时间,若规定时间内没有收到数据//则不监控该rfds。则若为NULL,一直等到接收到数据//再继续程序执行。printf("retval=%d--rfds_Socket=%d\n",rfds_Socket);if(retval==0){
    printf("select error\n");continue;}while(1){
    if(FD_ISSET(connfd, &rfds_Socket)) //接收到数据了{
    bzero(buf, MAXDATASIZE);int  len = recv(connfd, buf, MAXDATASIZE, 0);if(len<=0){
    //close(connfd);printf("client has closed!\n");connfd=-1;break;}printf("%s***%d\n",buf,len);send(connfd, buf, len, 0);memset(buf, '\0', MAXDATASIZE/sizeof (char));}}}close(connfd);close(listenfd);return 0;
}

网络助手作为客户端,本例(Beagle Bone)作为服务端,助手连接成功之后,Beagle Bone将收到网络助手的数据再转发回去。

助手IP:192.168.0.100 端口:53544
服务器(Beagle Bone)IP:192.168.0.152 端口:51230
Linux下C语言编程(4):TCP/IP 网络通信

Linux下C语言编程(4):TCP/IP 网络通信
上一篇:Linux下C语言编程(3):Uart编程
下一篇:Linux下C语言编程(5):多线程编程