代码说明:
本程序使用TCP相关函数构建通信,使用pthread文件构建多线程实现同时收发。
一个服务器可连接多个客户端,可接收多个客户端发送的文件信息,但是服务器发送由于键盘输入,所以不确定回复给哪个客户端。
服务器使用8080端口,客户端使用8081端口(可改)
相关代码:
//客户端
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <iostream>
#include <vector>
#include <thread>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <csignal>
using namespace std;
const int clientcom = 8081; //设置客户端端口
const int servercom = 8080; //设置服务器监听端口
const char serverip[] = "192.168.124.3"; //设置服务器IP
bool flag = true; //开关变量
void *threadsend(void *fd);
void *threadrecv(void *fd);
int main()
{
struct sockaddr_in serveraddr;
struct sockaddr_in clientaddr;
socklen_t serverlen = sizeof(serveraddr);
socklen_t clientlen = sizeof(clientaddr);
int clientfd; //连接文件描述符
//填写服务器信息
memset(&serveraddr, 0, serverlen);
serveraddr.sin_family = AF_INET; //IPV4协议
serveraddr.sin_port = htons(servercom); //设置服务器端口
serveraddr.sin_addr.s_addr = inet_addr(serverip); //服务器IP
//填写客户端(本机)信息
memset(&clientaddr, 0, clientlen);
clientaddr.sin_family = AF_INET; //IPV4协议
clientaddr.sin_port = htons(clientcom); //设置端口
clientaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//创建文件描述符
if ((clientfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
cout << "creat fd fail" << endl;
return -1;
}
//绑定fd与客户端(本机)地址信息
if ((bind(clientfd, (sockaddr *)&clientaddr, clientlen)) < 0)
{
cout << "bind fd fail" << endl;
return -1;
}
//与服务器连接
if (connect(clientfd, (struct sockaddr *)&serveraddr, serverlen) < 0)
{
cout << "connect fail" << endl;
return -1;
}
//连接成功,创建两个线程进行读写操作
pthread_t t_send;
pthread_t t_recv;
pthread_create(&t_send, NULL, threadsend, &clientfd);
pthread_create(&t_recv, NULL, threadrecv, &clientfd);
//阻塞,等待返回
pthread_join(t_send, NULL);
pthread_join(t_recv, NULL);
close(clientfd);
return 0;
}
//写线程
void *threadsend(void *fd)
{
char sendbuff[100];
int sendfd = *((int *)fd); //强制转换为int
while (flag)
{
cout << "Input" << endl;
cin.getline(sendbuff, 100); //等待写入
if ((send(sendfd, sendbuff, 100, 0)) < 0)
{
cout << "send fail" << endl;
}
}
return 0;
}
//读线程
void *threadrecv(void *fd)
{
int count;
int recvfd = *((int *)fd); //强制转换为int
char recvbuff[100];
while (flag)
{
count = 0;
count = recv(recvfd, recvbuff, 100, 0);
if (count < 0)
{
cout << "recv fail" << endl;
}
else if (count > 0)
{
recvbuff[count] = 0; //在接收信息后面加终止符
cout << "recv :" << recvbuff << endl;
}
}
return 0;
}
//服务端
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <iostream>
#include <vector>
#include <thread>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <pthread.h>
using namespace std;
const int servercom = 8080; //设置服务器监听端口
void *threadcreat(void *fd);
void *threadrecv(void *fd);
void *threadsend(void *fd);
int main()
{
struct sockaddr_in serveraddr;
struct sockaddr_in clientaddr;
socklen_t serverlen = sizeof(serveraddr);
socklen_t clientlen = sizeof(clientaddr);
int listenfd;
vector<int> connectfd(10, -1); //连接符
int connectnumber = 0;
//填写服务器信息
memset(&serveraddr, 0, serverlen);
serveraddr.sin_family = AF_INET; //IPV4协议
serveraddr.sin_port = htons(servercom); //设置端口
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置任意本机IP
//创建文件描述符
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
cout << "creat fd fail" << endl;
return -1;
}
//绑定fd与地址信息
if ((bind(listenfd, (sockaddr *)&serveraddr, serverlen)) < 0)
{
cout << "bind fd fail" << endl;
return -1;
}
//设置监听队列
if (listen(listenfd, 1024) < 0)
{
cout << "listen fd fail" << endl;
return -1;
}
cout << "server init ok" << endl;
while (1)
{
//每来一个连接请求都会进行一次连接,客户端信息存放在clientaddr中
if ((connectfd[connectnumber] = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen)) < 0)
{
cout << "connect error!" << endl;
return -1;
}
pthread_t tid; //生成子线程标识符号
pthread_create(&tid, NULL, threadcreat, &connectfd[connectnumber]);
connectnumber++;
cout << "client num:" << connectnumber << endl;
}
return 0;
}
//发送线程
void *threadsend(void *fd)
{
int sendfd = *((int *)fd);
char temp[100];
while (1)
{
memset(temp, 0, sizeof(temp));
cout << "Input" << endl;
cin.getline(temp, 100); //等待写入
if ((send(sendfd, temp, 100, 0)) < 0)
{
cout << "send fail" << endl;
}
}
close(sendfd);
return 0;
}
//接收线程
void *threadrecv(void *fd)
{
int recvfd = *((int *)fd);
int count;
char temp[100];
while (1)
{
count = 0;
count = recv(recvfd, temp, 100, 0);
if (count < 0)
{
cout << "recv fail" << endl;
}
else if(count>0)
{
temp[count] = 0; //在接收信息后面加终止符
cout << "recv :" << temp << endl;
}
}
close(recvfd);
return 0;
}
void *threadcreat(void *fd)
{
pthread_t t_send, t_recv;
pthread_create(&t_send, NULL, threadsend, fd);
pthread_create(&t_recv, NULL, threadrecv, fd);
return 0;
}
心得:
client客户端编写思路:
1.声明服务器端地址serveraddr 和 客户端服务地址clientaddr
2.填写服务器信息
3.填写客户端自己的信息,主要是绑定端口
4.创建文件描述符fd,fd与本机地址绑定bind,connect函数与服务器连接
5.创建两个线程,一个负责读,一个负责写,传入参数fd
6.主程序中使用join阻塞,线程中使用while(1)一直循环
server服务端编写思路:
1.声明服务器端地址serveraddr 和 客户端服务地址clientaddr
2.填写服务器信息,主要是监听端口信息
3.创建文件描述符fd,fd与服务器绑定bind,设置监听队列
4.主程序进入while(1),使用accept来接收客户端的连接请求,生成的连接文件描述符clientfd存放于数组
5.创建一个新线程t,传入文件描述符clientfd
6.在线程中再次创建两个线程,负责读取和发送,传入clientfd
注意事项:
1.每次发送数据前,使用memset将数组清零
2.每次接收数据后,需要将存储数组后加0以保证显示顺利
3.多线程传入函数形式如下
void *threadrecv(void *fd)
需要将传入参数强制转化为: int recvfd = *((int *)fd);
4.多线程注意头文件的包含,修改CMakeLists文件
多线程pthread使用:
1.头文件中包含#include <pthread.h>;
2.CMakeLists中添加以下命令
find_package(Threads REQUIRED);
target_link_libraries(s1 Threads::Threads)
3.程序中相关命令
pthread_t tid1; //声明一个线程标识符
pthread_create(&tid1, NULL, threadsend, fd); //创建一个线程,四个参数分别为线程标识符、线程属性、线程函数、线程参数
pthread_join(tid1,NULL); //两个参数为被等待的线程标识符、一个用户定义的指针,它可以用来存储被等待线程的返回值。为阻塞函数
pthread_exit(&val); //在线程中使用,中断线程,唯一的参数是函数的返回代码,传给join中的第二个元素