基于TCP的多线程双向通信
今天我们来简单实现一下TCP的双向通信
1. 服务器端的搭建流程
1.先获取套接字(socket)
2.绑定套接字(bind),公布自己的网路信息(IP地址,端口号,家族协议)
3.开始监听(listen),设置决连数(设置连接队列的客户端个数)
4.等待客户端连接,获取到客户端的对等套接字,该套接字用于通信(accept)
5.连接成功后开始通信(write,read)
6.关闭套接字(close)
整体代码采用的是总分总格式
废话不多说,先上代码
这个是程序中所用到的结构体:
typedef struct S_Inf
{
int s_fd;//服务器自己的套接字
int c_fd;//客户端的对等套接字
pthread_t send_id;//用于发送信息的线程id号
pthread_t recv_id;//用于接收信息的线程id号
}SI,*P_SI;
mian函数:
int main(void)
{
//初始化
P_SI p_si = Ser_Init();
if(p_si == (P_SI)-1)
{
perror("初始化失败\n");
return -1;
}
//客户端与服务端通信
if(Speaking(p_si) == -1)
{
printf("通信失败\n");
return -1;
}
//释放内存
if(Free(p_si) == -1)
{
printf("释放失败\n");
return -1;
}
//主进程挂起
//线程是依赖于该主进程的,进程生命周期已结束,线程也跟着结束,所以这里挂起,不让他死掉
pause();
return 0;
}
初始化函数:
P_SI Ser_Init()
{
P_SI p_si = (P_SI)malloc(sizeof(SI));
memset(p_si,0,sizeof(SI));
//创建套接字
p_si->s_fd = socket(AF_INET,SOCK_STREAM,0);
if(p_si->s_fd == -1)
{
perror("socket");
return (P_SI)-1;
}
printf("创建套接字成功\n");
//定义服务器结构体,用于存放服务器IP,端口号等信息
struct sockaddr_in s_inf;
memset(&s_inf,0,sizeof(s_inf));
s_inf.sin_family = AF_INET; // 家族协议,用的是IPV4的,所以用AF_INET
s_inf.sin_addr.s_addr = htonl(INADDR_ANY);
s_inf.sin_port = htons(8888);//端口号,
//绑定套接字
int bind_ret = bind(p_si->s_fd,(struct sockaddr *)&s_inf,sizeof(s_inf));
if(bind_ret == -1)
{
perror("bind");
return (P_SI)-1;
}
printf("绑定成功\n");
//开始监听
int listen_ret = listen(p_si->s_fd,1);//这里的1表示决连数
if(listen_ret == -1)
{
perror("listen");
return (P_SI)-1;
}
printf("正在监听\n");
printf("正在等待连接\n");
struct sockaddr_in u_inf;
memset(&u_inf,0,sizeof(u_inf));
int len = sizeof(u_inf);
p_si->c_fd = accept(p_si->s_fd,(struct sockaddr *)&u_inf,&len);
if(p_si->c_fd == -1)
{
perror("accept");
return (P_SI)-1;
}
printf("%s已连接\n",inet_ntoa(u_inf.sin_addr));
return p_si;
}
通信:
int Speaking(P_SI p_si)
{
//创建线程给客户端发送信息
if(pthread_create(&p_si->send_id,NULL,send_msg,(void *)&p_si->c_fd)!=0)
{
perror("pthread_create");
return -1;
}
//创建线程接收客户端信息
if(pthread_create(&p_si->recv_id,NULL,recv_msg,(void *)&p_si->c_fd)!=0)
{
perror("pthread_create");
return -1;
}
return 0;
}
发送信息:
void *send_msg(void *arg)
{
int u_fd = *((int *)arg);
char buf[1024];
while(1)
{
memset(buf,0,sizeof(buf));
printf("请输入要发送的信息:");
scanf("%s",buf);
if(write(u_fd,&buf,strlen(buf)) == -1)
{
perror("write");
pthread_exit(NULL);
}
}
}
接收信息:
void *recv_msg(void *arg)
{
int u_fd = *((int *)arg);
char buf[1024];
while(1)
{
memset(buf,0,sizeof(buf));
int read_ret = read(u_fd,&buf,sizeof(buf));
if(read_ret==-1)
{
perror("read");
pthread_exit(NULL);
}else if(read_ret == 0)//当读取字节数为0时,证明客户端掉线了
{
printf("客户端掉线\n");
pthread_exit(NULL);
}
else{
//打印客户端发来的信息
printf("客户端说:%s\n",buf);
}
}
}
释放:
int Free(P_SI p_si)
{
if(pthread_join(p_si->send_id,NULL)!=0)
{
perror("pthread_join");
return -1;
}
if(pthread_join(p_si->recv_id,NULL)!=0)
{
perror("pthread_join");
return -1;
}
close(p_si->s_fd);//关闭套接字
free(p_si);
return 0;
}
2. 客户端的搭建流程:
1.先获取套接字(socket)
2.连接服务器(connect)
3.开始通信(write,read)
4.关闭套接字(close)
以下是客户端的代码
这是用于存放客户端信息的结构体:
typedef struct C_Inf
{
int c_id;
pthread_t send_id;
pthread_t recv_id;
}CI,*P_CI;
main函数:
int main(void)
{
P_CI p_ci = Cli_Init();
if(p_ci == (P_CI)-1)
{
printf("初始化失败\n");
return -1;
}
if(Speaking(p_ci) == -1)
{
printf("通信失败\n");
return -1;
}
if(Free(p_ci) == -1)
{
printf("内存释放失败\n");
return -1;
}
pause();
return 0;
}
初始化函数:
P_CI Cli_Init()
{
P_CI p_ci = (P_CI)malloc(sizeof(CI));
if(p_ci == NULL)
{
return (P_CI)-1;
}
//获取套接字
p_ci->c_id = socket(AF_INET,SOCK_STREAM,0);
if(p_ci->c_id == -1)
{
perror("socket");
return (P_CI)-1;
}
//创建结构体用于存放服务器的信息
struct sockaddr_in ser_inf;
memset(&ser_inf,0,sizeof(ser_inf));
ser_inf.sin_addr.s_addr = inet_addr("192.168.244.134");//这个IP是自己你所要连接的服务器的IP,要自己修改
ser_inf.sin_family = AF_INET;//IPv4用的是AF_INET这个家族协议
ser_inf.sin_port = htons(8888);//端口号要和服务器的端口号一致
//开始连接服务器
int connect_ret = connect(p_ci->c_id,(struct sockaddr *)&ser_inf,sizeof(ser_inf));
if(connect_ret == -1)
{
perror("connect");
return (P_CI)-1;
}
return p_ci;
}
通信:
int Speaking(P_CI p_ci)
{
//创建两条线程用于收发信息
if(pthread_create(&p_ci->send_id,NULL,send_msg,&p_ci->c_id) != 0)
{
perror("pthread_create");
return -1;
}
if(pthread_create(&p_ci->recv_id,NULL,recv_msg,&p_ci->c_id) != 0)
{
perror("pthread_create");
return -1;
}
return 0;
}
发送信息:
void *send_msg(void *arg)
{
int c_id = *((int *)arg);
char buf[1024];
while(1)
{
memset(buf,0,sizeof(buf));
printf("请输入要发送的信息:\n");
scanf("%s",buf);
if(write(c_id,&buf,strlen(buf)) == -1)
{
perror("write");
pthread_exit(NULL);
}
}
}
接收信息:
void *recv_msg(void *arg)
{
int c_id = *((int *)arg);
char buf[1024];
while(1)
{
memset(buf,0,sizeof(buf));
int read_ret = read(c_id,&buf,sizeof(buf));
if(read_ret==-1)
{
perror("read");
pthread_exit(NULL);
}else if(read_ret == 0)
{
printf("服务器掉线\n");
pthread_exit(NULL);
}
else{
printf("服务器说:%s\n",buf);
}
}
}
释放:
int Free(P_CI p_ci)
{
//回收线程
pthread_join(p_ci->send_id,NULL);
pthread_join(p_ci->recv_id,NULL);
close(p_ci->c_id);//关闭套接字
//释放内存
free(p_ci);
return 0;
}
服务器和客户端之间通信的代码都差不多,就是初始化的时候有点区别,客户端的步骤少一点,他只需要向服务器发送连接请求,连接成功后便能通信。
运行结果
百度网盘
代码链接:https://pan.baidu.com/s/1FvHVdkNbMf2ef1l1arao-A
提取码:abcd