Linux C语言 31-网络编程之TCP例程
本节关键字:C语言 网络编程 套接字操作 TCP协议 服务端 客户端 非阻塞
相关C库函数:setsockopt, socket, bind, listen, accept, recv, send, close, select, connect
相关接口介绍
Linux C语言 30-套接字操作
例程执行任务说明
本例程中服务端的任务:
- 等待新的客户端连接
- 读取已连接客户端发来的消息并回复
- 断开并移除连接异常的客户端
本例程中客户端的任务:
- 创建5个客户端,并使每个客户端都成功连接服务端
- 间隔1秒,发送消息告知服务端自己的套接字
- 通信次数达到10次时,断开当前连接
- 处于未连接状态时自动进行服务端重连
TCP协议服务端例程实现
// tpcserver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define MAX_CLIENT_CNT 10typedef struct
{int socket;int state;int cnt;
} TcpClient;typedef struct
{TcpClient s[10]; int count;
} SocketArray;void msleep(int msecs);
int addSocket(SocketArray *array, int socket);
int delSocket(SocketArray *array, int socket);
int getOneServerSocket(const char *ip, int port);int main(int argc, char *argv[])
{int rc, rxn, txn, srvfd, port;char ip[32] = {0};int i, maxfd;fd_set readfds;SocketArray sArray;bzero(&sArray, sizeof(sArray));char rxBuffer[1024] = {0};char txBuffer[1024] = {0};if (argc != 3){printf("please input correct arguments:\n");printf("\ttcpserver ip port\n\n");return -1;}// 解析ip和portstrcpy(ip, argv[1]);port = atoi(argv[2]);printf("ip: %s, port: %d\n", ip, port);// 创建套接字srvfd = getOneServerSocket(ip, port);// 循环非阻塞读取套接字上的数据while (1){maxfd = 0;FD_ZERO(&readfds);// 将服务端套接字加入select队列FD_SET(srvfd, &readfds);if (srvfd > maxfd)maxfd = srvfd;// 将所有已连接客户端的套接字加入select队列for (i=0; i<sArray.count; i++){FD_SET(sArray.s[i].socket, &readfds);if (sArray.s[i].socket > maxfd)maxfd = sArray.s[i].socket;}// 非阻塞等待套接字传来数据struct timeval timeout = {2, 0}; // 2sif (select(maxfd+1, &readfds, NULL, NULL, &timeout) <= 0){msleep(5);continue;}// 先确认监听套接字if (FD_ISSET(srvfd, &readfds)){int newfd, addrlen;struct sockaddr_in naddr;addrlen = sizeof(naddr);newfd = accept(srvfd, (struct sockaddr*)&naddr, &addrlen);if (newfd == -1){perror("accept");}else{printf("add new client[%s:%d], socket: %d\n", inet_ntoa(naddr.sin_addr), ntohs(naddr.sin_port), newfd);addSocket(&sArray, newfd);}}// 再确认客户端套接字for (i=0; i<sArray.count; i++){if (FD_ISSET(sArray.s[i].socket, &readfds)){bzero(rxBuffer, sizeof(rxBuffer));rxn = recv(sArray.s[i].socket, rxBuffer, sizeof(rxBuffer)-1, 0);if (rxn <= 0){printf("socket exception, socket: %d\n", sArray.s[i].socket);delSocket(&sArray, sArray.s[i].socket);continue;}printf("client[%d]: %s\n", sArray.s[i].socket, rxBuffer);bzero(txBuffer, sizeof(txBuffer));sprintf(txBuffer, "server reply client[%d]: %s", sArray.s[i].socket, rxBuffer);txn = send(sArray.s[i].socket, txBuffer, strlen(txBuffer), 0);if (txn <= 0){printf("socket exception, socket: %d\n", sArray.s[i].socket);delSocket(&sArray, sArray.s[i].socket);continue;}}}}for (i=0; i<sArray.count; i++)close(sArray.s[i].socket);close(srvfd);return 0;
}void msleep(int msecs)
{struct timeval stoptime;stoptime.tv_sec = (int)(msecs/1000);stoptime.tv_usec = (int)(msecs%1000)*1000;select(0, 0, 0, 0, &stoptime);
}int addSocket(SocketArray *array, int socket)
{if (array->count >= MAX_CLIENT_CNT){printf("The number of clients has reached the maximum limit(%d)\n", MAX_CLIENT_CNT);return -1;}int i, exists=0;for (i=0; i<array->count; i++){if (array->s[i].socket == socket){exists = 1;break;}}if (exists == 1){printf("client exists, socket: %d, index: %d\n", array->s[i].socket, i);return socket;}array->s[array->count].socket = socket;array->count += 1;printf("add client[%d], client count: %d\n", array->s[array->count].socket, array->count);return socket;
}int delSocket(SocketArray *array, int socket)
{if (array->count <= 0){printf("No client\n");return -1;}int i;SocketArray tmpArr;bzero(&tmpArr, sizeof(tmpArr));for (i=0; i<array->count; i++){if (array->s[i].socket == socket){printf("remove client, socket: %d\n", socket);close(socket);continue;}tmpArr.s[tmpArr.count].socket = array->s[i].socket;tmpArr.count += 1;}memset(array, 0, sizeof(SocketArray));memcpy(array, &tmpArr, sizeof(tmpArr));return socket;
}int getOneServerSocket(const char *ip, int port)
{int rc, srvfd, reused, timeout;struct sockaddr_in addr;srvfd = socket(AF_INET, SOCK_STREAM, 0);if (srvfd == -1){perror("create socket");exit(-1);}// 设置端口复用reused = 1;rc = setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, &reused, sizeof(reused));if (rc == -1){perror("SO_REUSEDADDR");goto EXIT;}// 设置发送超时限制timeout = 1000; // 1秒setsockopt(srvfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));if (rc == -1){perror("SO_SNDTIMEO");goto EXIT;}// 套接字绑定本地的ip和portaddr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);bzero(addr.sin_zero, sizeof(addr.sin_zero));rc = bind(srvfd, (struct sockaddr*)&addr, sizeof(addr));if (rc == -1){perror("bind");goto EXIT;}// 监听套接字rc = listen(srvfd, 10);if (rc == -1){perror("listen");goto EXIT;}return srvfd;EXIT:close(srvfd);exit(-1);
}
###TCP协议客户端例程
// tcpclient.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define MAX_CLIENT_CNT 5
#define OFFLINE 0
#define ONLINE 1
#define MAX_MSG_SEND 10typedef struct
{int socket;int state;int cnt;
} TcpClient;typedef struct
{TcpClient s[10];int count;
} SocketArray;void msleep(int msecs);
int addSocket(SocketArray *array, int socket);
int delSocket(SocketArray *array, int socket);
int getOneClientSocket(const char *ip, int port);
void setClientState(SocketArray *array, int fd, int state);
int connectToServer(int fd, const char *ip, int port);int main(int argc, char *argv[])
{int rc, clifd, rxn, txn, port;char ip[32] = {0};int i, j, maxfd;fd_set readfds;SocketArray sArray;char rxBuffer[1024] = {0};char txBuffer[1024] = {0};if (argc != 3){printf("please input correct arguments:\n");printf("\ttcpclient ip port\n\n");return -1;}// 解析ip和portstrcpy(ip, argv[1]);port = atoi(argv[2]);printf("ip: %s, port: %d\n", ip, port);while (1){bzero(&sArray, sizeof(sArray));// 创建 MAX_CLIENT_CNT 个客户端for (i=0; i<MAX_CLIENT_CNT; i++){clifd = getOneClientSocket(ip, port);if (clifd == -1)continue;printf("create client, socket: %d\n", clifd);addSocket(&sArray, clifd);}// 所有离线客户端连接服务端for (i=0; i<sArray.count; i++){if (sArray.s[i].state == OFFLINE){rc = connectToServer(sArray.s[i].socket, ip, port);if (rc == 0)setClientState(&sArray, sArray.s[i].socket, ONLINE);elsesetClientState(&sArray, sArray.s[i].socket, OFFLINE);}}// 所有在线客户端主动向服务端打招呼for (i=0; i<sArray.count; i++){if (sArray.s[i].state == ONLINE){bzero(txBuffer, sizeof(txBuffer));sprintf(txBuffer, "Hello, i'm client[%d], i will disconnect after sending data %d times", sArray.s[i].socket, MAX_MSG_SEND-sArray.s[i].cnt);txn = send(sArray.s[i].socket, txBuffer, strlen(txBuffer), 0);if (txn <= 0){printf("socket exception, socket: %d\n", sArray.s[i].socket);setClientState(&sArray, sArray.s[i].socket, OFFLINE);continue;}sArray.s[i].cnt += 1;}msleep(100 * i);}// 连接成功的客户端循环执行任务while (1){j = 0;for (i=0; i<sArray.count; i++){if (sArray.s[i].state == OFFLINE)close(sArray.s[i].socket);elsej++;}sArray.count = j;if (sArray.count <= 0){printf("No connected clients\n");break;}maxfd = 0;FD_ZERO(&readfds);// 将所有已连接客户端的套接字加入select队列for (i=0; i<sArray.count; i++){if (sArray.s[i].state == OFFLINE)continue;FD_SET(sArray.s[i].socket, &readfds);if (sArray.s[i].socket > maxfd)maxfd = sArray.s[i].socket;}// 非阻塞等待套接字传来数据struct timeval timeout = {2, 0}; // 2sif (select(maxfd+1, &readfds, NULL, NULL, &timeout) <= 0){msleep(5);continue;}// 处理已连接套接字上的消息及数据for (i=0; i<sArray.count; i++){if (sArray.s[i].state == OFFLINE)continue;if (FD_ISSET(sArray.s[i].socket, &readfds)){// 发送消息数量达到 MAX_MSG_SEND 的客户端主动断开连接if (sArray.s[i].cnt >= MAX_MSG_SEND){printf("i'm client[%d], i will disconnect now\n", sArray.s[i].socket);setClientState(&sArray, sArray.s[i].socket, OFFLINE);continue;}bzero(rxBuffer, sizeof(rxBuffer));rxn = recv(sArray.s[i].socket, rxBuffer, sizeof(rxBuffer)-1, 0);if (rxn <= 0){printf("socket exception, socket: %d\n", sArray.s[i].socket);setClientState(&sArray, sArray.s[i].socket, OFFLINE);continue;}printf("client[%d] recv: %s\n", sArray.s[i].socket, rxBuffer);bzero(txBuffer, sizeof(txBuffer));sprintf(txBuffer, "Hello, i'm client[%d], i will disconnect after sending data %d times", sArray.s[i].socket, MAX_MSG_SEND-sArray.s[i].cnt);txn = send(sArray.s[i].socket, txBuffer, strlen(txBuffer)+1, 0);if (txn <= 0){printf("socket exception, socket: %d\n", sArray.s[i].socket);setClientState(&sArray, sArray.s[i].socket, OFFLINE);continue;}sArray.s[i].cnt += 1;}msleep(100 * i);}msleep(1000);}msleep(1000);}for (i=0; i<sArray.count; i++){close(sArray.s[i].socket);}return 0;
}void msleep(int msecs)
{struct timeval stoptime;stoptime.tv_sec = (int)(msecs/1000);stoptime.tv_usec = (int)(msecs%1000)*1000;select(0, 0, 0, 0, &stoptime);
}int addSocket(SocketArray *array, int socket)
{if (array->count >= MAX_CLIENT_CNT){printf("The number of clients has reached the maximum limit(%d)\n", MAX_CLIENT_CNT);return -1;}int i, exists=0;for (i=0; i<array->count; i++){if (array->s[i].socket == socket){exists = 1;break;}}if (exists == 1){printf("client exists, socket: %d, index: %d\n", socket, i);return socket;}array->s[array->count].socket = socket;array->s[array->count].state = OFFLINE;array->count += 1;printf("add client[%d], client count: %d\n", array->s[i].socket, array->count);return socket;
}int delSocket(SocketArray *array, int socket)
{if (array->count <= 0){printf("No client\n");return -1;}int i;SocketArray tmpArr;bzero(&tmpArr, sizeof(tmpArr));for (i=0; i<array->count; i++){if (array->s[i].socket == socket){printf("remove client, socket: %d\n", socket);close(socket);continue;}tmpArr.s[tmpArr.count].socket = array->s[i].socket;tmpArr.s[tmpArr.count].state = array->s[i].state;tmpArr.count += 1;}memset(array, 0, sizeof(SocketArray));memcpy(array, &tmpArr, sizeof(tmpArr));return socket;
}int getOneClientSocket(const char *ip, int port)
{int rc, clifd, reused, timeout;clifd = socket(AF_INET, SOCK_STREAM, 0);if (clifd == -1){perror("create socket");return -1;}// 设置端口复用reused = 1;rc = setsockopt(clifd, SOL_SOCKET, SO_REUSEADDR, &reused, sizeof(reused));if (rc == -1){perror("SO_REUSEDADDR");goto EXIT;}// 设置发送超时限制timeout = 1000; // 1秒setsockopt(clifd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));if (rc == -1){perror("SO_SNDTIMEO");goto EXIT;}return clifd;EXIT:close(clifd);return -1;
}int connectToServer(int fd, const char *ip, int port)
{int rc;struct sockaddr_in addr;// 套接字绑定服务端的ip和portaddr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);bzero(addr.sin_zero, sizeof(addr.sin_zero));// 连接服务端rc = connect(fd, (struct sockaddr*)&addr, sizeof(addr));if (rc == -1){printf("failed to connect server[%s:%d], socket: %d\n", ip, port, fd);return -1;}printf("client connect succeed, socket: %d\n", fd);return 0;
}void setClientState(SocketArray *array, int fd, int state)
{int i;for (i=0; i<array->count; i++){if (array->s[i].socket == fd){array->s[i].state = state;break;}}
}
例程编译及运行
TCP服务端例程编译及运行结果
#假设主机ip为192.168.201.28
$ gcc tcpserver.c -o tcpserver
$ ./tcpserver 0.0.0.0 66666
ip: 0.0.0.0, port: 66666
add new client[192.168.146.128:57654], socket: 4
add new client[192.168.146.128:57655], socket: 5
add new client[192.168.146.128:57656], socket: 6
add new client[192.168.146.128:57657], socket: 7
add new client[192.168.146.128:57658], socket: 8
client[4]: Hello, i'm client[3], i will disconnect after sending data 10 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 10 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 10 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 10 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 10 times
client[4]: Hello, i'm client[3], i will disconnect after sending data 9 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 9 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 9 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 9 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 9 times
client[4]: Hello, i'm client[3], i will disconnect after sending data 8 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 8 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 8 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 8 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 8 times
...
client[4]: Hello, i'm client[3], i will disconnect after sending data 1 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 1 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 1 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 1 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 1 times
socket exception, socket: 4
remove client, socket: 4
socket exception, socket: 6
remove client, socket: 6
socket exception, socket: 8
remove client, socket: 8
socket exception, socket: 5
remove client, socket: 5
socket exception, socket: 7
remove client, socket: 7
add new client[192.168.146.128:57855], socket: 4
add new client[192.168.146.128:57856], socket: 5
add new client[192.168.146.128:57857], socket: 6
add new client[192.168.146.128:57858], socket: 7
add new client[192.168.146.128:57859], socket: 8
client[4]: Hello, i'm client[3], i will disconnect after sending data 10 times
client[5]: Hello, i'm client[4], i will disconnect after sending data 10 times
client[6]: Hello, i'm client[5], i will disconnect after sending data 10 times
client[7]: Hello, i'm client[6], i will disconnect after sending data 10 times
client[8]: Hello, i'm client[7], i will disconnect after sending data 10 times
TCP客户端例程编译及运行结果
# 新打开一个终端
$ gcc tcpclient.c -o tcpclient
$ ./tcpclient 192.168.201.28 66666ip: 192.168.146.128, port: 66666
create client, socket: 3
add client[3], client count: 1
create client, socket: 4
add client[4], client count: 2
create client, socket: 5
add client[5], client count: 3
create client, socket: 6
add client[6], client count: 4
create client, socket: 7
add client[7], client count: 5
client connect succeed, socket: 3
client connect succeed, socket: 4
client connect succeed, socket: 5
client connect succeed, socket: 6
client connect succeed, socket: 7
client[3] recv: server reply client[4]: Hello, i'm client[3], i will disconnect after sending data 10 times
client[4] recv: server reply client[5]: Hello, i'm client[4], i will disconnect after sending data 10 times
client[5] recv: server reply client[6]: Hello, i'm client[5], i will disconnect after sending data 10 times
client[6] recv: server reply client[7]: Hello, i'm client[6], i will disconnect after sending data 10 times
client[7] recv: server reply client[8]: Hello, i'm client[7], i will disconnect after sending data 10 times
client[3] recv: server reply client[4]: Hello, i'm client[3], i will disconnect after sending data 9 times
client[4] recv: server reply client[5]: Hello, i'm client[4], i will disconnect after sending data 9 times
client[5] recv: server reply client[6]: Hello, i'm client[5], i will disconnect after sending data 9 times
client[6] recv: server reply client[7]: Hello, i'm client[6], i will disconnect after sending data 9 times
client[7] recv: server reply client[8]: Hello, i'm client[7], i will disconnect after sending data 9 times
...
client[3] recv: server reply client[4]: Hello, i'm client[3], i will disconnect after sending data 2 times
client[4] recv: server reply client[5]: Hello, i'm client[4], i will disconnect after sending data 2 times
client[5] recv: server reply client[6]: Hello, i'm client[5], i will disconnect after sending data 2 times
client[6] recv: server reply client[7]: Hello, i'm client[6], i will disconnect after sending data 2 times
client[7] recv: server reply client[8]: Hello, i'm client[7], i will disconnect after sending data 2 times
i'm client[3], i will disconnect now
i'm client[4], i will disconnect now
i'm client[5], i will disconnect now
i'm client[6], i will disconnect now
i'm client[7], i will disconnect now
No connected clients
create client, socket: 3
add client[3], client count: 1
create client, socket: 4
add client[4], client count: 2
create client, socket: 5
add client[5], client count: 3
create client, socket: 6
add client[6], client count: 4
create client, socket: 7
add client[7], client count: 5
client connect succeed, socket: 3
client connect succeed, socket: 4
client connect succeed, socket: 5
client connect succeed, socket: 6
client connect succeed, socket: 7
client[3] recv: server reply client[4]: Hello, i'm client[3], i will disconnect after sending data 10 times
client[4] recv: server reply client[5]: Hello, i'm client[4], i will disconnect after sending data 10 times
client[5] recv: server reply client[6]: Hello, i'm client[5], i will disconnect after sending data 10 times
client[6] recv: server reply client[7]: Hello, i'm client[6], i will disconnect after sending data 10 times
client[7] recv: server reply client[8]: Hello, i'm client[7], i will disconnect after sending data 10 times
网络编程之TCP协议例程小结
本节展示的例程只是基础的通信框架,还未嵌套较为完善的通信协议,感兴趣的小伙伴可以自行补充完善(期待大家的分享-)。后期有时间的话,我准备加入Modbus协议,欢迎小伙伴们积极收藏关注私信交流!