在上一篇发布的是简单UDP网络程序
链接:UDP网络程序
TCP是建立连接的可靠传输,相较于UDP来说接口可能会更复杂一些。
用到的socket接口
////创建socket文件描述符(客户端+服务器端,TCP/UDP) //int socket(int domain,int type,int protocol); ////绑定端口号(服务器 TCP,UDP) //int bind(int socket,const struct sockaddr *address,socklen_t address_len); ////服务器开始监听,被动状态,可以被建立连接(服务器端 TCP) //int listen(int socket,int backlog); ////接收请求(服务器端 TCP) //int accept(int socket,struct sockaddr *address,socklen_t address_len); ////建立连接(客户端 TCP) //int connect(int sockfd,const struct sockaddr *addr,socklen_t address_len);
理解这几个函数
int socket(int domain,int type,int protocol);
socket()函数打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,domain 为指定协议族 ,例如IPv4为AF_INET,type 为协议类型,例如 TCP 为SOCK_STREAM;
int listen(int socket,int backlog);
这个函数的作用是使服务器处于被动监听状态(理解为将内核态的连接转为用户态),也可以理解为将套接字的状态设置为监听状态,一旦被设置为监听状态,就可以接受新的连接。
当服务器压力较小时,用户发出一个连接请求,服务器就可以处理一个连接请求,但是当服务器压力较大时,服务器会来不及处理用户的连接请求,系统维护了一个等待队列,第二个参数为等待队列最大长度,即等待被建立连接的最大数量,一旦有其它用户断开与该服务器的连接,则等待队列中的用户就可以与该服务器建立连接了。backlog不能太大,一般在5 ~ 10左右,一方面是服务器处理能力限制,一方面是因为维护这个队列也需要花费资源。
int accept(int socket,struct sockaddr *address,socklen_t address_len);
在上面的listen()函数调用完毕之后,socket为监听状态,这个socket用于获取新的连接
在accept()函数调用成功后,会返回一个新的文件描述符new_socket,这个new_socket才是真正用来数据通信。
service.c
#include <stdio.h> #include <error.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> //简单TCP网络程序,可以实现简单聊天室 //******************服务器端******************** //TCP协议是建立连接的可靠传输协议 //1.绑定Ip 和 端口号 //2.被动打开,使可以被建立连接 //3.接受客户端的连接请求 //开始进行事件循环 // 4.接收来自客户端的请求 // 5.开始计算处理客户端发来的请求 // a)因为这里是回显服务,没有过多的计算处理,只做以下处理 // b)将客户端的请求显示在标准输出上 // c)再将请求发回给客户端 // 6.对客户端发来的请求进行响应 void ProcessConnect(int listen_socket) { char buf[1024]={0}; sockaddr_in peer; //用来处理一次连接。需要循环的处理 while(1) { //7.接收来自客户端的请求 ssize_t read_size=read(listen_socket,buf,sizeof(buf)-1); if(read_size<0) { continue; } if(read_size==0) { printf("client discount!\n"); //用来标识用户已经关闭连接 close(listen_socket); return; } buf[read_size]='\0'; //8.将客户端的请求显示再标准输出上 printf("peer %s:%d say %s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf); //9.将请求发送给客户端 write(listen_socket,buf,read_size); } } // ./service [IP] [Port] int main(int argc,char * argv[]) { //1.检查命令行参数 if(argc!=3) { printf("Usage: ./service [IP] [Port]\n"); return 1; } //2.创建socket int fd=socket(AF_INET,SOCK_STREAM,0); if(fd<0) { perror("socket"); return 1; } //3.将要绑定的IP 和 Prot 存储在 sockaddr_in 结构体中 sockaddr_in addr; addr.sin_family=AF_INET; socklen_t addr_len=sizeof(addr); //将点分十进制的IP地址 转化 为32位数字 addr.sin_addr.s_addr=inet_addr(argv[1]); //将端口号转位网络字节序 addr.sin_port=htons(atoi(argv[2])); //4.服务器建立连接 int bind_ret=bind(fd,(sockaddr *)&addr,addr_len); if(bind_ret<0) { perror("bind"); return 1; } //5.服务器开始被动打开,处于可以被建立连接状态 int listen_ret=listen(fd,5); if(listen_ret<0) { perror("listen"); return 1; } //开始事件循环 while(1) { //6.接受客户端的连接请求 //fd 这个socket用来监听 int listen_socket=accept(fd,(sockaddr *)&addr,&addr_len); //这里的返回值是一个新的 socket 将内核态建立的连接放到用户态执行 //后续信息传输建立在这个socket if(listen_socket<0) { perror("accept"); continue; } //7.处理一次连接 ProcessConnect(listen_socket); } close(fd); return 0; }
client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> //********************简单TCP网络程序 //********************客户端************************* // //TCP协议是建立连接的可靠传输 //1.与服务器建立连接 //2.向服务器发送请求 //3.接收来自服务器端响应 int main(int argc ,char *argv[]) { //检查命令行参数 if(argc!=3) { printf("Usage : ./client [IP] [Port]\n"); } //1.创建socket int fd=socket(AF_INET,SOCK_STREAM,0); if(fd<0) { perror("socket"); return 1; } //2.建立连接 //将要建立连接的服务器的IP 和 Port 放在结构体中 sockaddr_in service; service.sin_family=AF_INET; socklen_t service_len=sizeof(service); service.sin_addr.s_addr=inet_addr(argv[1]); service.sin_port=htons(atoi(argv[2])); int connect_ret=connect(fd,(sockaddr *)&service,service_len); if(connect_ret<0) { perror("connect"); return 1; } char buf[1024]={0}; while(1) { //3.向服务器发送请求 //从标准输入开始读取数据 ssize_t read_size=read(0,buf,sizeof(buf)-1); if(read_size<0) { perror("read"); return 1; } if(read_size==0) { printf("done\n"); return 0; } buf[read_size]='\0'; write(fd,buf,strlen(buf)); //4.从服务器接收请求 //构造结构体存放接收方的信息 //这里的service会作为输出型参数,因为服务器端是任意Ip主机都保持连接状态,这里接收服务的参数,然后打印出来 char buf_service[1024]={0}; sockaddr_in peer; read_size=read(fd,buf_service,sizeof(buf_service)-1); if(read_size<0) { perror("recvfrom"); return 1; } if(read_size==0) { printf("read done\n"); return 0; } buf_service[read_size]='\0'; //5.将从服务器接收的信息显示到标准输出 printf("service[%s:%d] response :%s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf_service); } }
其实这种服务器模式是没有什么实际作用的,我们发现这种但执行流的方案,服务器只能串行的处理一个用户的请求。
后面我们会实现一个服务器卡可以同时处理多个客户端的请求和响应
链接一://TODO
链接二://TODO
完。