编程语言
884
在掌握了基于TCP的套接字通信流程之后,为了方便使用,提高编码效率,可以对通信操作进行封装,先基于C语言进行面向过程的函数封装,再基于C++进行面向对象的类封装。
基于C语言的封装
基于TCP的套接字通信分为两部分:服务器端通信和客户端通信。只要掌握了通信流程,封装出对应的功能函数也就不在话下了,回顾一下通信流程:
- 服务器端
- 创建用于监听的套接字
- 将用于监听的套接字和本地的IP以及端口进行绑定
- 启动监听
- 等待并接受新的客户端连接,连接建立得到用于通信的套接字和客户端的IP、端口信息
- 使用得到的通信的套接字和客户端通信(接收和发送数据)
- 通信结束,关闭套接字(监听 + 通信)
- 客户端
- 创建用于通信的套接字
- 使用服务器端绑定的IP和端口连接服务器
- 使用通信的套接字和服务器通信(发送和接收数据)
- 通信结束,关闭套接字(通信)
1.1 函数声明
通过通信流程可以看出服务器和客户端有些操作步骤是相同的,因此封装的功能函数是可以共用的,相关的通信函数声明如下:
/////////////////////////////////////////////////// //////////////////// 服务器 /////////////////////// /////////////////////////////////////////////////// int bindSocket(int lfd, unsigned short port); int setListen(int lfd); int acceptConn(int lfd, struct sockaddr_in *addr); /////////////////////////////////////////////////// //////////////////// 客户端 /////////////////////// /////////////////////////////////////////////////// int connectToHost(int fd, const char* ip, unsigned short port); /////////////////////////////////////////////////// ///////////////////// 共用 //////////////////////// /////////////////////////////////////////////////// int createSocket(); int sendMsg(int fd, const char* msg); int recvMsg(int fd, char* msg, int size); int closeSocket(int fd); int readn(int fd, char* buf, int size); int writen(int fd, const char* msg, int size);
关于函数readn()和writen()的作用 参考 TCP数据粘包处理
1.2 函数定义
// 创建监套接字 int createSocket() { int fd = socket(AF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); return -1; } printf("套接字创建成功, fd=%d\n", fd); return fd; } // 绑定本地的IP和端口 int bindSocket(int lfd, unsigned short port) { struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); saddr.sin_addr.s_addr = INADDR_ANY; // 0 = 0.0.0.0 int ret = bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); return -1; } printf("套接字绑定成功, ip: %s, port: %d\n", inet_ntoa(saddr.sin_addr), port); return ret; } // 设置监听 int setListen(int lfd) { int ret = listen(lfd, 128); if(ret == -1) { perror("listen"); return -1; } printf("设置监听成功...\n"); return ret; } // 阻塞并等待客户端的连接 int acceptConn(int lfd, struct sockaddr_in *addr) { int cfd = -1; if(addr == NULL) { cfd = accept(lfd, NULL, NULL); } else { int addrlen = sizeof(struct sockaddr_in); cfd = accept(lfd, (struct sockaddr*)addr, &addrlen); } if(cfd == -1) { perror("accept"); return -1; } printf("成功和客户端建立连接...\n"); return cfd; } // 接收数据 int recvMsg(int cfd, char** msg) { if(msg == NULL || cfd <= 0) { return -1; } // 接收数据 // 1. 读数据头 int len = 0; readn(cfd, (char*)&len, 4); len = ntohl(len); printf("数据块大小: %d\n", len); // 根据读出的长度分配内存 char *buf = (char*)malloc(len+1); int ret = readn(cfd, buf, len); if(ret != len) { return -1; } buf[len] = '\0'; *msg = buf; return ret; } // 发送数据 int sendMsg(int cfd, char* msg, int len) { if(msg == NULL || len <= 0) { return -1; } // 申请内存空间: 数据长度 + 包头4字节(存储数据长度) char* data = (char*)malloc(len+4); int bigLen = htonl(len); memcpy(data, &bigLen, 4); memcpy(data+4, msg, len); // 发送数据 int ret = writen(cfd, data, len+4); return ret; } // 连接服务器 int connectToHost(int fd, const char* ip, unsigned short port) { // 2. 连接服务器IP port struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); inet_pton(AF_INET, ip, &saddr.sin_addr.s_addr); int ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr)); if(ret == -1) { perror("connect"); return -1; } printf("成功和服务器建立连接...\n"); return ret; } // 关闭套接字 int closeSocket(int fd) { int ret = close(fd); if(ret == -1) { perror("close"); } return ret; } // 接收指定的字节数 // 函数调用成功返回 size int readn(int fd, char* buf, int size) { int nread = 0; int left = size; char* p = buf; while(left > 0) { if((nread = read(fd, p, left)) > 0) { p += nread; left -= nread; } else if(nread == -1) { return -1; } } return size; } // 发送指定的字节数 // 函数调用成功返回 size int writen(int fd, const char* msg, int size) { int left = size; int nwrite = 0; const char* p = msg; while(left > 0) { if((nwrite = write(fd, msg, left)) > 0) { p += nwrite; left -= nwrite; } else if(nwrite == -1) { return -1; } } return size; }