当前位置: 代码迷 >> 综合 >> TCP的socket通讯
  详细解决方案

TCP的socket通讯

热度:100   发布时间:2023-11-15 01:02:01.0

之前写过UDP的socket通讯,下面将介绍TCP的socket通讯,同时提供了TCP客户端与并发服务器端的几个案例。

目录

简介

 socket开发API介绍

TCP并发服务器


简介

与UDP的区别

 

                 TCP

                        UDP

是否面向连接

                    ?

                        X

是否可靠

                    ?

                        X

支持广播、多播

                    X

                        ?

效率

                    低

                        高

客户端与服务器TCP通讯流程

                                                     

 socket开发API介绍

1、创建tcp套接字socket()

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if ( sockfd < 0 )
{perror("socket");exit(-1);
}

开发TCP客户端需要满足以下几点:

        1、知道服务器的ip、port;

        2、需主动连接服务器;

        3、使用函数:socket()、connect()、send()、recv()、close()

Note:tcp的一些函数没有目的指向,比如是 send() 而不是 sendto(),这是因为 tcp 客户端与服务器通讯之前是先建立连接后通讯,使用该函数时,已经知晓目的地址。


2、connect连接函数

//主动与服务器建立连接
#include <sys/socket.h>
int connect(int sockfd;const struct sockaddr *addr,socklen_t len
);
参数介绍sockfd	:	socketaddr	:	连接的服务器地址结构len		:	地址结构体长度
Return成功返回0
Noteconnect建立连接后不会产生新的套接字成功建立连接后才可以传输数据

3、send数据发送函数

//发送数据
#include <sys/socket.h>
ssize_t send(int sockfd,const void * buf,size_t nbtyes,int flags
);
参数sockfd	:socketbuf		:发送的数据nbytes	:发送缓存数据大小flags	:套接字标志(通常为0)
Return	成功发送的数据字节数

4、recv函数

//接收网络数据,阻塞式
#include <sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t nbytes,int flags
);
参数sockfd	:socketbuf		:接收网络数据的缓冲区nbytes	:接收缓冲区的大小(以字节为单位)flags	:套接字标志(通常为0)
Return成功接收的字节数

客户端发送与接受数据

以下代码将发送一次数据和接受数据,服务器可使用 NetAssist(windows)、linux下可使用自己开发的

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
int main(void)
{//create tcp socketint sockfd = socket(AF_INET,SOCK_STREAM,0);if (sockfd < 0){printf("CREATE SOCKET ERROR\n");return 0;}//bind是可选的, 固定ip和portstruct sockaddr_in client;bzero(&client,sizeof(client));client.sin_family = AF_INET;client.sin_port = htons(8000);client.sin_addr.s_addr = htonl(INADDR_ANY);bind(sockfd,,(struct sockaddr *)&client, sizeof(client));//connect 连接服务器struct sockaddr_in ser_addr;bzero(&ser_addr,sizeof(ser_addr));ser_addr.sin_port = htons(8080);ser_addr.sin_family = AF_INET;ser_addr.sin_addr.s_addr = inet_addr("192.168.31.96");connect(sockfd, (struct sockaddr *)&ser_addr,sizeof(ser_addr));//给服务器发送数据char buf[128] = "";printf("please enter the msg to send\n");fgets(buf,sizeof(buf),stdin);buf[strlen(buf) - 1] = 0;//消除回车的影响send(sockfd, buf, strlen(buf), 0);//接收数据char msg[128] = "";recv(sockfd, msg, sizeof(msg), 0);printf("received msg is %s\n",msg);close(sockfd);return 0;
}

开发服务器设计需要满足以下两点:

        1、确定的地址和端口

        2、监听(listen)与接收(accept)

设计流程:

        1、socket()(主动)

        2、bind() 固定ip和固定端口

        3、listen()(主动变被动、创建连接队列)

        4、accept() (从连接队列中提取成功建立的连接,接下来可以正式通讯)

        根据上图TCP通讯流程图可以看出,服务器绑定固定端口,监听客户端的请求。客户端与服务器通讯前,要先与服务器端成功建立连接,需要经过三次握手。成功建立的连接会放入“完成链接”,服务器将使用accept()函数从该队列中取出客户端请求并处理。


1、listen()函数

//将套接字由主动改为被动
//操作系统为该套接字设置一个队列,用来记录所有链接到该套接字的连接
int listen(int sockfd, int backlog);
参数sockfd		:socket监听套接字backlog	:连接队列的长度
Return	成功返回 0

2、accept()函数

//从已建立队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen
);
参数sockfd	:socketcliaddr	:用于存放客户端socket的地址结构addrlen:套接字地址结构体长度的地址
Return	已连接的socket

TCP应用层无法发送长度为0的数据报,因为为0表示这通讯结束。

案例代码

可以上述client联合使用,客户端连接服务器的端口要与服务器端绑定的端口一致。

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>
#include <fcntl.h>
int main(void)
{//1、创建一个tcp监听套接字int sockfd = socket(AF_INET,  SOCK_STREAM, 0);//2、使用bind函数给监听套接字绑定固定的ip和端口struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(8080);server.sin_addr.s_addr = htonl(INADDR_ANY);bind(sockfd,(struct sockaddr *)&server, sizeof(server));//3、使用listen函数连接队列(主动变被动)  listen(sockfd, 10);  //4、使用accept函数从连接队列中提取已经完成的连接struct sockaddr_in client;bzero(&client,sizeof(client));socklen_t len = sizeof(client);//fd代表index客户端连接,client存储客户端的信息int fd = accept(sockfd, (struct sockaddr *)&client, &len);char ip[16] = "";inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, 16);printf("client %s connected\n",ip);//5、获取客户端的请求,以及回应客户端char buf[128] = "";recv(fd, buf, sizeof(buf), 0);printf("client request is %s\n",buf);send(fd,"recv", strlen("recv"), 0);//6、关闭已连接套接字close(fd);//7、关闭监听套接字close(sockfd);return 0;
}

TCP并发服务器

可以同时服务多个客户端。TCP并发服务器本质还是TCP服务器,同时其又可以服务多个客户端。以下将提供两种方法:多进程与多线程

多线程

客户端正常退出不会有影响,但如果服务器端意外退出,再次运行bind的时候需要防止端口被占用(5~6分钟)。

#include <stdio.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>
#include <fcntl.h>
void *deal_client(void *sockfd);
int main(void)
{//1、创建TCP监听套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("CREATE SOCKET!!");}//端口复用int yes = 1;setsockopt(sockfd, SOL_SOCKET,  SO_REUSEADDR, &yes, sizeof(yes));//2、给socket绑定ip和端口信息struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(8088);server.sin_addr.s_addr = htonl(INADDR_ANY);int result = bind(sockfd, (struct sockaddr *)&server, sizeof(server));if (result == -1){perror("bind");}// 3、调用listen,将sockfd调整主动变为被动listen(sockfd, 10);//4、提取建立完成的链接//accept调用一次只能接待一个客户端,需要反复调用acceptwhile(1){struct sockaddr_in client;socklen_t len = sizeof(client);int new_fd = accept(sockfd, (struct sockaddr *)&client, &len);//遍历客户端信息char  ip[16] = "";unsigned short port = ntohs(client.sin_port);inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, 16);printf("client %s is connected %hu port\n", ip, port);//为每个客户端创建线程,单独的服务客户端pthread_t tid;pthread_create(&tid, NULL, deal_client, (void *)&new_fd);//线程分离pthread_detach(tid);}close(sockfd);return 0;
}
//TCP并发(并发服务器核心代码都不相同)
void *deal_client(void *sockfd)
{//通过arg获得已连接socket//给服务器发啥就回啥int fd = *(int *)sockfd;//服务器核心代码,处理客户端请求、while (1){char data[128] = "";int len = recv(fd, data, sizeof(data), 0);if (!len){break;}send(fd, data, len, 0);}close(fd);
}

多进程

父子进程、资源独立、某个进程结束不会影响已有进程,服务器更加稳定,代价是多进程会浪费资源

#include <stdio.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
void deal_client(int sockfd);
int main(void)
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket");}int yes;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));struct sockaddr_in server;server.sin_family = AF_INET;server.sin_addr.s_addr = htonl(INADDR_ANY);server.sin_port = htons(8088);bind(sockfd, (struct sockaddr *)&server, sizeof(server));listen(sockfd, 10);while (1){struct sockaddr_in client;socklen_t len = sizeof(client);int sock_new = accept(sockfd, (struct sockaddr *)&client, &len);unsigned short port = ntohs(client.sin_port);char ip[16] = "";inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, 16);printf("client ip %s connected at port %hu\n",ip,port);//子进程//使用fork后,会复制父进程的全部状态资源,注意下面四个关闭socket,描述符if (fork() == 0){//关闭监听socket(进程中的,以防止与父进程冲突)close(sockfd);//服务于客户端的核心代码deal_client(sock_new);//关闭已连接socket(进程中的,以防止与父进程冲突)close(sock_new);_exit(-1);}else //父进程{//关闭父进程中的已连接socketclose(sock_new);}}//关闭父进程中的监听socketclose(sockfd);return 0;
}
void deal_client(int sockfd)
{while (1){char buf[128] = "";int len = recv(sockfd, buf, sizeof(buf), 0);if (!len){break;}//printf("the msg is %s\n",buf);send(sockfd, buf, len, 0);}
}

 

  相关解决方案