前言
本文主要介绍了linux下标准的TCP通信流程,实现了客户端和服务器的通信,主要实现了消息的回发,即服务器将消息原封不动的回发给客户端。如果对接口不熟悉可以参考socket api介绍或者参考其他博客。
客户端代码
#include <errno.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #define SERVER_PORT 9999 //服务器端口 #define SERVER_IP "192.168.179.128" //服务端IP #define MAXLEN 4096 //缓冲区最大大小 int main(){ int socketfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(SERVER_IP); servaddr.sin_port = htons(SERVER_PORT); int ret = connect(socketfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr_in)); if(ret != 0){ printf("connect server failed:error number:%d\n", errno); exit(-1); } while(1){ const char *message = "this is from client message"; ret = send(socketfd, message, strlen(message), 0); if(ret > 0){ char recvbuf[MAXLEN] = {0}; ret = recv(socketfd, recvbuf, MAXLEN, 0); if(ret > 0){ printf("recv message from server:%s\n", recvbuf); }else if(ret == 0){ printf("server has closed!\n"); close(socketfd); } else{ printf("recv error :%d\n", errno); close(socketfd); } } sleep(1); } return 0; }
服务端代码
#include <errno.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #define MAXLNE 4096 int main(int argc, char **argv) { int listenfd, connfd, n; struct sockaddr_in servaddr; char buff[MAXLNE]; if ((listenfd = 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_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(9999); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) { printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } //listen接口之后会一直监听客户端的连接,每次客户端连接,都会和其创建连接(三次连接时内核完成的,不是由应用程序去控制的) //三次握手不发生在任何API中,协议栈本身被动完成的。 if (listen(listenfd, 10) == -1) { printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } //这个版本只能用于多个客户端连接,而只有第一个客户端可以发送数据 //只accept第一个客户端的连接(也就是只会与第一个客户端进行通信) struct sockaddr_in client; socklen_t len = sizeof(client); if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) { printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } printf("========waiting for client's request========\n"); while (1) { //阻塞等待第一个客户端的数据的到来 n = recv(connfd, buff, MAXLNE, 0); if (n > 0) { buff[n] = '\0'; printf("recv msg from client: %s\n", buff); send(connfd, buff, n, 0); } else if (n == 0) { close(connfd); } //close(connfd); } close(listenfd); return 0; }
代码思考
以上代码为什么可以支持多个客户端的连接,却只有第一个连接的客户端可以发送数据给服务器?
关于为什么支持多个客户端的连接,代码里已经有说明,如何实现多客户端给服务器发送数据呢,多线程或者I/O多路复用可以解决。
支持多客户端的服务端代码版本
一客户端一线程
这种方式是早期的网络服务器模型,不过目前由于客户端很多时候都是百万甚至千万级别的,所以这个只适用于支持少量客户端的情况,主要是因为服务器不可能支持太多线程的原因
#include <errno.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <pthread.h> #define MAXLNE 4096 //一个线程8M,4G内存,最多512个线程(仅仅是线程就占用了4个G) //C10k void *client_routine(void *arg) { // int connfd = *(int *)arg; char buff[MAXLNE]; while (1) { int n = recv(connfd, buff, MAXLNE, 0); if (n > 0) { buff[n] = '\0'; printf("recv msg from client: %s\n", buff); send(connfd, buff, n, 0); } else if (n == 0) { close(connfd); break; } } return NULL; } int main(int argc, char **argv) { int listenfd, connfd, n; struct sockaddr_in servaddr; char buff[MAXLNE]; if ((listenfd = 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_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(9999); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) { printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } //listen接口之后会一直监听客户端的连接,每次客户端连接,都会和其创建连接(三次连接时内核完成的,不是由应用程序去控制的) //三次握手不发生在任何API中,协议栈本身被动完成的。 if (listen(listenfd, 10) == -1) { printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } while (1) { struct sockaddr_in client; socklen_t len = sizeof(client); if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) { printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno); return 0; } //1个客户端1个线程去处理,早期的网络模型就是这种模式 //缺点:不适用于当前高并发的情况。 //优点:逻辑简单 pthread_t threadid; pthread_create(&threadid, NULL, client_routine, (void*)&connfd); } close(listenfd); return 0; }
I/O多路复用版本
select版本
poll版本
epoll版本