经过两天的学习,终于做出来可以向指定客户端发送数据的TCP服务,写本次博文的目的有二,一是将学习成果分享给正在学习TCP并发服务的同行,二是整理一下笔记,方便日后复习。首先说一下为什么学习TCP并发服务,6个月前去面试嵌入式软件工程师的面试题是“用两个半小时的时间用任意语言编写一个可以两个及两个以上客户端定向通信的TCP服务”,经过三个小时的时间也没有写出来,最近做物联网的项目,又用到了这个东西,因此觉得有必要深入研究一番。
言归正传。该服务端代码可实现,两个客户端的定向聊天,客户端连接服务端的第一件事事就是登陆,用于区别身份。测试方法是,将服务端启动后,开启两个可以收发的客户端,可以使用TCP调试助手,第一个客服端和服务器连接成功后,发送client1,注意此时千万不要发第二帧数据,因为代码还没有作相应的完善,这个时候要先注册第二个客户端,连接成功后,发送client2,这个时候两个客户端就注册成功了,特别注意,当其中一个客户端掉线后,另一个客户端就不要发数据了,等待新的客户端注册成功后再发数据。
下面是服务端代码:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <pthread.h>
#include <semaphore.h>
#define SERV_PORT 5001
#define SEVR_IP_ADDR "192.168.32.128" //已经用不到了
#define BACKLOG 5 //同时允许几个客户端正在连接 2*BACKLOG+1
#define QUIT_STR "quit"
#define CLIENT1 "client1"
#define CLIENT2 "client2"
void *cli_data_handle(void *arg);
void *sendToGateway(void *arg);
void *sendToHttp(void *arg);
int gateway = -1; //存储网关sockfd
int httpClient = -1; //存储http服务器fd
int client_Init = 0; //当两个客户端都连接成功后,才开始发送数据
sem_t sem_gateway, sem_http; //信号量
char buf[BUFSIZ];
int main(int argc, char *argv[])
{
int fd = -1;
struct sockaddr_in sin;
/******************************************/
if(sem_init(&sem_gateway, 0, 0) < 0) //定义信号量,初值为零
{
perror("sem_init");
exit(-1);
}
if(sem_init(&sem_http, 0, 0) < 0) //定义信号量,初值为0
{
perror("sem_init");
exit(-1);
}
/******************************************/
//1创建socket fd
if((fd = socket(AF_INET, SOCK_STREAM,0)) < 0)
{
perror("socket");
exit(1);
}
int b_reuse = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); //服务器可以快速重启
//2绑定
//2.1 填充struct sockaddr_in结构体变量
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);//和网卡无关的IP地址应用
//2.2绑定
if(bind(fd, (struct sockaddr *)&sin, sizeof(sin))<0)
{
perror("bind");
exit(1);
}
//3 listen()把主动套接字变成被动套接字
if(listen(fd, BACKLOG) < 0)
{
perror("listen");
exit(1);
}
//4 阻塞等待客户端的连接请求
int newfd = -1;
pthread_t tid;
pthread_t tid2;
pthread_t tid3;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
pthread_create(&tid2, NULL, sendToGateway, NULL); //创建转发线程
pthread_create(&tid3, NULL, sendToHttp, NULL); //创建转发线程
while(1)
{
if((newfd = accept(fd, (struct sockaddr *)&cin, &addrlen)) < 0)
{
perror("accept");
exit(1);
}
char ipv4_addr[16];
if( !inet_ntop(AF_INET, (void *)&cin.sin_addr.s_addr, ipv4_addr, sizeof(cin)) ) //把网络地址变成字符串形式的
{
perror("inet_ntop");
exit(1);
}
printf("Client(%s:%d) is connected!\n", ipv4_addr, htons(cin.sin_port));
pthread_create(&tid, NULL, cli_data_handle, (void *)&newfd);
}
close(fd);
return 0;
}
void *sendToGateway(void *arg)
{
//int newfd = *(int *)arg;
while(1)
{
printf("进入sendToGateway线程\n");
sem_wait(&sem_http); //等待收到来自HTTP的数据
printf("start2\n");
send(gateway,buf,strlen(buf),0);
printf("end2\n");
}
}
void *sendToHttp(void *arg)
{
while(1)
{
printf("进入sendToHttp线程\n");
sem_wait(&sem_gateway); //等待收到来自网关的数据
printf("start2\n");
send(httpClient,buf,strlen(buf),0);
printf("end2\n");
}
}
void *cli_data_handle(void *arg)
{
int newfd = *(int *)arg;
printf("handle thread:newfd = %d\n", newfd);
//5 读写
int ret = -1;
while(1)
{
bzero(buf, BUFSIZ);
do{
ret = read(newfd, buf, BUFSIZ-1);
}while(ret<0&&EINTR==errno);
if(ret<0)
{
perror("recv");
exit(1);
}
if(!ret) //对方已关闭
{
break;
}
printf("Receive data: %s\n", buf);
//判断是否是网关或者http
if(strncasecmp(buf, CLIENT1 , strlen(CLIENT1)) == 0 && client_Init < 2)
{
gateway = newfd;
client_Init ++;
printf("gateway sockfd:%d\n", gateway);
}
else if(strncasecmp(buf, CLIENT2, strlen(CLIENT2)) == 0 && client_Init < 2)
{
httpClient = newfd;
client_Init ++;
printf("httpClient sockfd:%d\n", httpClient);
}
else
{
printf("gateway sockfd:%d\n", gateway);
printf("httpClient sockfd:%d\n", httpClient);
printf("newfd:%d\n", newfd );
}
printf("client_Init :%d\n\n", client_Init);
if(newfd == gateway)
{
sem_post(&sem_gateway); //收到网关数据 V操作
}
else if(newfd == httpClient)
{
sem_post(&sem_http); //收到http数据 V操作
}
if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR)))//用户输入了quit字符
{
printf("client(fd=%d) is exiting!\n", newfd);
break;
}
}
close(newfd);
client_Init --;
}