本节关键字:C语言 网络编程 套接字操作 TCP协议 服务端 客户端 非阻塞
相关C库函数:setsockopt, socket, bind, listen, accept, recv, send, close, select, connect
Linux C语言 30-套接字操作
本例程中服务端的任务:
本例程中客户端的任务:
// tpcserver.c
#include
#include
#include
#include
#include
#include
#include
#define MAX_CLIENT_CNT 10
typedef 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和port
strcpy(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}; // 2s
if (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和port
addr.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
#include
#include
#include
#include
#include
#define MAX_CLIENT_CNT 5
#define OFFLINE 0
#define ONLINE 1
#define MAX_MSG_SEND 10
typedef 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和port
strcpy(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);
else
setClientState(&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);
else
j++;
}
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}; // 2s
if (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和port
addr.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;
}
}
}
#假设主机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
# 新打开一个终端
$ gcc tcpclient.c -o tcpclient
$ ./tcpclient 192.168.201.28 66666
ip: 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
本节展示的例程只是基础的通信框架,还未嵌套较为完善的通信协议,感兴趣的小伙伴可以自行补充完善(期待大家的分享-)。后期有时间的话,我准备加入Modbus协议,欢迎小伙伴们积极收藏关注私信交流!