在我的另一篇博客中介绍了Linux下TCP的通信流程以及简单TCP通信实例的实现。在学习网络编程时,经常看到这样一句话:“只有使用了select函数才能写出像样的网络程序”,所以这篇博客分享一个带有select函数实现的TCP多线网络通信。服务器代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <pthread.h>
#define SERVER_PORT 2828
#define LISENT_NUM 10
#define RECEIVE_LEN 128
//发送线程全局变量
int send_Thread_Run = 1;
int lisent_Thread_Run = 1;
int receive_Thread_Run = 1;
int fdmax = 0;
int Client_index = 0;
int client[LISENT_NUM] = { -1 };
void* lisent_Thread(void * arg)
{
int sfd, cfd;
struct sockaddr_in clientaddr;
struct sockaddr_in serverAddr;
int size = sizeof(struct sockaddr);
if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(-1);
}
memset(&serverAddr, 0, sizeof(struct sockaddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(SERVER_PORT);
if (bind(sfd, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
close(sfd);
exit(-1);
}
if (listen(sfd, LISENT_NUM) == -1)
{
perror("listen");
close(sfd);
exit(-1);
}
printf("Enter into lisent thread, PORT=%d\n", SERVER_PORT);
while (lisent_Thread_Run)
{
if ((cfd = accept(sfd, (struct sockaddr *)&clientaddr, (socklen_t*)&size)) == -1)
{
perror("accept");
close(sfd);
exit(-1);
}
fdmax = fdmax > cfd ? fdmax : cfd;
printf("client connect success! cfd = %d fdmax = %d\n", cfd, fdmax);
client[Client_index] = cfd;
cfd = 0;
Client_index += 1;
printf("---client%d (ip = %s : SERVER_PORT = %d) connect success\n", Client_index, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
}
printf("listen thread exit!\n");
}
void* send_Thread(void * arg)
{
int len, iflag, ret = 1;
char buff[1024];
printf("Enter into send thread!\n");
while (send_Thread_Run)
{
int i = 0;
if ((Client_index > 0) && (Client_index < LISENT_NUM))
{
usleep(1000 * 500);
for (i = 0; i < Client_index; i++)
{
if (send(client[i], "hello", 6, MSG_NOSIGNAL) == -1)
{
perror("send");
//if (close(client[i]) >= 0)
// printf("---client%d cfd=%d exit ---\n", i + 1, client[i]);
for (; i < Client_index; i++)
{
client[i] = client[i + 1];
}
client[Client_index] = -1;
Client_index -= 1;
}
}
}
}
printf("send thread exit!\n");
for (int i = 0; i < Client_index; i++)
{
if (client[i] != -1)
close(client[i]);
}
}
void* receive_Thread(void * arg)
{
int ret = 0, flag = 0;
char buf[RECEIVE_LEN];
fd_set fdr;
struct timeval SelectTimeOut;
printf("Enter into receive thread!\n");
while (receive_Thread_Run)
{
int selectNum = 0;
FD_ZERO(&fdr);
if (Client_index)
{
for (int i = 0; i < Client_index; i++)
{
if (client[i] > 0)
FD_SET(client[i], &fdr);
}
}
SelectTimeOut.tv_sec = 5;
SelectTimeOut.tv_usec = 500;
//selectNum = select(FD_SETSIZE, &fdr, NULL, NULL, &SelectTimeOut);
selectNum = select(fdmax + 1, &fdr, NULL, NULL, &SelectTimeOut);
switch (selectNum)
{
case -1:
printf("selectNum = %d\n", selectNum);
perror("select error");
break;
//exit(-1);
case 0:
continue;
default:
printf("selectNum = %d\n", selectNum);
for (int i = 0; i < Client_index; i++)
{
if (FD_ISSET(client[i], &fdr))
{
ret = recv(client[i], buf, sizeof(buf), 0);
if (ret > 0)
{
printf("client%d receive: %s %d\n", i + 1, buf, ret);
}
else if (ret == 0)
{
if (close(client[i]) >= 0)
{
printf("close socket success cfd=%d \n", client[i]);
FD_CLR(client[i], &fdr);
for (int j = 0; j < Client_index; j++)
{
client[j] = client[j + 1];
}
client[Client_index] = -1;
Client_index -= 1;
printf("--- Client number = %d client%d exit---\n", Client_index, i + 1);
}
else
printf("close socket failed cfd=%d \n", client[i]);
}
else
{
perror("recv");
exit(-1);
}
memset(buf, 0, RECEIVE_LEN);
}
}
break;
}
}
}
int main(int argc, char * argv[])
{
int sfd, cfd;
int iflag, ret;
struct sockaddr_in clientaddr;
struct sockaddr_in serverAddr;
char buff[1024];
int size = sizeof(struct sockaddr);
pthread_t sendThead, lisentThead, receiveThead;
printf("main:server waiting connect...\n");
pthread_create(&lisentThead, NULL, lisent_Thread, NULL);
pthread_create(&sendThead, NULL, send_Thread, NULL);
pthread_create(&receiveThead, NULL, receive_Thread, NULL);
while (1)
{
}
printf("main thread exit...\n");
return 0;
}
说明一下,select函数主要用来实现服务器的多路监听,所以,这里只给出了服务器的代码。客户端仍然与简单TCP通信中相似,或者优化一下阻塞模式为非阻塞模式即可。
上述代码已亲测可用,但是这里主线程在启动三个子线程后就自动退出了,将监听线程作为主线程使用更为合理,但这里就不改了,现在手里没有板子,怕影响最后代码的质量。