(7条消息) Linux下TCP/IP编程
本文参考自徐晓鑫《后台开发》,重点复习总结TCP通信流程,读者也可以参考:
http://blog.csdn.net/wqc_csdn/article/details/51513543,谢谢。
一、客户端和服务端操作流程
服务器端:
socket() --> bind() --> listen() --> accept() --> recv() --> close()
创建socket --> 绑定socket和端口号–> 监听端口号–> 接收来自客户端的连接请求–> 从socket中读取字符–> 关闭socket
客户端:
socket() --> connect() --> send() --> close()
创建socket --> 连接指定服务器的IP/端口号–>向socket中写入信息–>关闭socket
二、代码
服务器端:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <unistd.h>#define MAXLINE 4096int main(){ int listenfd,connfd; struct sockaddr_in servaddr; char buff[4096]; int n; //创建一个TCP的socket if( (listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1) { printf(" create socket error: %s (errno :%d)\n",strerror(errno),errno); return 0; } //先把地址清空,检测任意IP memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(6666); //地址绑定到listenfd if ( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { printf(" bind socket error: %s (errno :%d)\n",strerror(errno),errno); return 0; } //监听listenfd if( listen(listenfd,10) == -1) { printf(" listen socket error: %s (errno :%d)\n",strerror(errno),errno); return 0; } printf("====waiting for client's request=======\n"); //accept 和recv,注意接收字符串添加结束符'\0' while(1) { if( (connfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) == -1) { printf(" accpt socket error: %s (errno :%d)\n",strerror(errno),errno); return 0; } n = recv(connfd,buff,MAXLINE,0); buff[n] = '\0'; printf("recv msg from client:%s\n",buff); close(connfd); } close(listenfd); return 0;}
客户端:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#define MAXLINE 4096int main(int argc, char**argv){ int sockfd,n; char recvline[4096],sendline[4096]; struct sockaddr_in servaddr; if(argc !=2) { printf("usage: ./client <ipaddress>\n"); return 0; } //创建socket if( (sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) { printf(" create socket error: %s (errno :%d)\n",strerror(errno),errno); return 0; } memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(6666); //IP地址从“点分十进制”转换到“二进制整肃” if( inet_pton(AF_INET,argv[1], &servaddr.sin_addr) <=0 ) { printf("inet_pton error for %s\n",argv[1]); return 0; } //连接 if( connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) <0) { printf(" connect socket error: %s(errno :%d)\n",strerror(errno),errno); return 0; } printf("send msg to server:\n"); fgets(sendline,4096,stdin); //send发送 if ( send(sockfd,sendline,strlen(sendline),0) <0) { printf("send msg error: %s(errno :%d)\n",strerror(errno),errno); return 0; } close(sockfd); return 0;}
运行:
g++ server.cpp -o server
g++ client.cpp -o client
分别打开两个终端窗口
一个执行./server命令,
一个执行./client 1227.0.0.1命令。
三、错误记录
在调试过程中发现错误:
socket error: Socket operation on non-socket(errno :88)
经过仔细分析发现:
if( (
sockfd = socket(AF_INET,SOCK_STREAM,0))
== -1)
是优先级出现了问题,这里必须先复制,之后进行关系运算符的比较。
本文风格的代码非常容易在这里犯错误,特此记录。
四、多线程并发服务器
2018年11月3日补充
多线程和多进程的处理方式类似,都是创建一个新的线程,客户端有请求时,用新创建的线程处理。
这里参考文章:多线程服务器,对代码进行模块化封装。
和上文相比,将客户端修改为循环发送,直至输入q
退出
犯的一个错误是服务器printf打印忘记加\n
,导致没有刷新缓冲区,只有客户端每次端开链接时才打印,汗~
//使用pthread线程库#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/socket.h>#include<sys/types.h>#include<sys/stat.h>#include<netinet/in.h>#include<arpa/inet.h>#include<pthread.h>#define PORT 6666 //服务器端口#define BACKLOG 5 //listen队列等待的连接数#define MAXDATASIZE 1024 //缓冲区大小 void process_cli(int connectfd, struct sockaddr_in client); //客户端请求处理函数void* start_routine(void* arg); //线程函数 typedef struct _ARG { int connfd; struct sockaddr_in client; }ARG; //客户端结构体void sys_err(const char * ptr_err){ perror(ptr_err); exit(EXIT_FAILURE);}//处理客户端链接的接收工作*void accept_conn(int listenfd){ int connectfd; //socket描述符 pthread_t connectthread; //线程体变量 ARG *arg; //客户端结构体变量 struct sockaddr_in client; //客户端地址信息结构体 int sin_size = sizeof(struct sockaddr_in); while(1) { //调用accept,返回与服务器连接的客户端描述符 if ((connectfd = accept(listenfd,(struct sockaddr *)&client,(socklen_t *)&sin_size))==-1) { sys_err("accept() error\n"); } arg = new ARG; arg->connfd = connectfd; memcpy(&arg->client, &client, sizeof(client)); //创建线程,以客户端连接为参数,start_routine为线程执行函数 if (pthread_create(&connectthread, NULL, start_routine, (void*)arg)) { sys_err("Pthread_create() error"); } }} void tcp_server(uint16_t port){int listenfd; //socket描述符 struct sockaddr_in server; //服务器地址信息结构体 //调用socket,创建监听客户端的socket if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { sys_err("Creating socket failed."); } //设置socket属性,端口可以重用 int opt = SO_REUSEADDR; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //初始化服务器地址结构体 bzero(&server,sizeof(server)); server.sin_family=AF_INET; server.sin_port=htons(PORT); server.sin_addr.s_addr = htonl (INADDR_ANY); //调用bind,绑定地址和端口 if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) { sys_err("Bind error."); } //调用listen,开始监听 if(listen(listenfd,BACKLOG) == -1){ sys_err("listen() error\n"); } //处理客户端链接的接收工作 accept_conn(listenfd);//关闭监听socket close(listenfd); }int main(){ uint16_t port = 6666;tcp_server(port); } void process_cli(int connectfd, sockaddr_in client){ int num; char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE]; printf("You got a connection from %s. \n",inet_ntoa(client.sin_addr) );//MSG_WAITALL while ((num = recv(connectfd, recvbuf, MAXDATASIZE,0)) > 0) { recvbuf[num] = '\0'; printf("Received size( %d ) message: %s\n",num, recvbuf);} /* num = recv(connectfd, cli_name, MAXDATASIZE,0); if (num == 0) { close(connectfd); return; } cli_name[num - 1] = '\0'; printf("Client's content is: %s\n",cli_name); while (num = recv(connectfd, recvbuf, MAXDATASIZE,0)) { recvbuf[num] = '\0'; printf("Received client( %s ) message: %s",cli_name, recvbuf); for (int i = 0; i < num - 1; i++) { sendbuf[i] = recvbuf[num - i -2]; } sendbuf[num - 1] = '\0'; send(connectfd,sendbuf,strlen(sendbuf),0); }*/printf("Client disconnected.\n"); close(connectfd); }void* start_routine(void* arg){ ARG *info = (ARG *)arg; process_cli(info->connfd, info->client);//删除对应的堆内存delete info; pthread_exit(NULL);}
客户端:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#define MAXLINE 4096int main(int argc, char**argv){ int sockfd,n; char recvline[4096],sendline[4096]; struct sockaddr_in servaddr; if(argc !=2) { printf("usage: ./client <ipaddress>\n"); return 0; } //创建socket if( (sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) { printf(" create socket error: %s (errno :%d)\n",strerror(errno),errno); return 0; } memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(6666); //IP地址从“点分十进制”转换到“二进制整肃” if( inet_pton(AF_INET,argv[1], &servaddr.sin_addr) <=0 ) { printf("inet_pton error for %s\n",argv[1]); return 0; } //连接 if( connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) <0) { printf(" connect socket error: %s(errno :%d)\n",strerror(errno),errno); return 0; } printf("send msg to server:\n"); while(1){fgets(sendline,sizeof(sendline),stdin);sendline[strlen(sendline)-1] = '\0';if(strncasecmp(sendline,"q",1) == 0) {printf("quit\n");break;}//send发送//if ( send(sockfd,sendline,strlen(sendline),0) < 0) {// printf("send msg error: %s(errno :%d)\n",strerror(errno),errno);// return 0;//}int sentbytes = send(sockfd,sendline,strlen(sendline),0); printf("sent:%d\n",sentbytes);//bzero(sendline, strlen(sendline));} close(sockfd); return 0;}