最后是重新学习【进阶把】要补充的。。。
线程和套接字
1.socket+pthread实现客户端和服务器一对一聊天
我的坑:虚拟机要改成多线程才行,不然单独测试多线程可能没问题,但是吧多线程运用到处理socket的收发信息时,就会出问题 可能是我电脑配置太low了吧。
其他坑,都百度下有答案。
大概思路:将收、发两个分别抽离成函数,然后在主函数中去用线程的形式进行调用。
不足:
简单的实现一次客户端和通信端连接的时候,是服务器是可以支持。
比如:
可以看到,服务器端一直在accept,这里打开了两个客户端,服务器都响应了,端口是不一样的。没有聊天,只能先指定收、发的内容。
但是换成线程之后,两边都将收、发放入线程,就可以实现两端聊天功能。此时服务器只能响应一个客户端(可能是我电脑支持的线程数只有两个,内核只有两个的原因。)。如果把accept装进线程,实现服务器多线程处理客户端,这个我更加没办法测试。所以只是实现了聊天功能。
因为线程执行的顺序不确定,所以看着有些乱。可以通过sleep(),pthread_join()等方法,加上逻辑,应该能美观吧。
聊天功能的代码如下:
server端。
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <iostream>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void sendmessage(int *sockfd){
while (1)
{
char send_data[124];
cout<<"please input message:";
cin>>send_data;
int sendlen = send(*sockfd,send_data,sizeof(send_data),0);
if(sendlen>0){
cout<<"==>>send messages to client is:"<<send_data<<endl;
}
}
close(*sockfd);
}
void recvmessage(int *clientfd){
while (1)
{
char clientdata[256]={0};
int len = recv(*clientfd,clientdata,256,0);
if(len>0){
cout<<"<<==get client data is:"<<clientdata<<endl;
}
}
close(*clientfd);
}
int main(){
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_addr.s_addr=htons(INADDR_ANY);
addr.sin_port = htons(8000);
cout<<"open port:8000"<<endl;
bind(sockfd,(sockaddr*)&addr,sizeof(addr));
listen(sockfd,100);//
sockaddr_in clientaddr;
socklen_t clientlen = sizeof(sockaddr_in);
int clientfd = 0;
char ip[INET_ADDRSTRLEN];
while(1){
clientfd = accept(sockfd,(sockaddr*)&clientaddr,&clientlen);
inet_ntop(AF_INET,&clientaddr.sin_addr,ip,INET_ADDRSTRLEN);
printf("open ip:port %s:%d\n",ip,ntohs(clientaddr.sin_port));
//使用线程的方式处理
pthread_t tid;
pthread_create(&tid,0,reinterpret_cast<void *(*)(void *)>(sendmessage),&clientfd);
cout<<"### pthread way*server* sendmessage pthread ID:"<<tid<<endl;
pthread_detach(tid);
pthread_join(tid,0);
// sleep(2);
pthread_t tid2;
pthread_create(&tid2,0,reinterpret_cast<void *(*)(void *)>(recvmessage),&clientfd);
cout<<"### pthread way*server* recvmessage pthread ID:"<<tid2<<endl;
pthread_detach(tid2);
pthread_join(tid2,0);
while (1)
{
// 没有这个,线程可能没时间来得及创建程序会出乎意料
}
}
close(sockfd);
return 0;
}
client客户端:
#include <arpa/inet.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <stdio.h>
using namespace std;
void sendmessage(int *sockfd){
char send_data[20];
while (1)
{
cout<<"please input message:";
cin>>send_data;
int sendlen = send(*sockfd,send_data,sizeof(send_data),0);
if(sendlen>0){
cout<<"---->send messages to server is:"<<send_data<<endl;
}else{
cout<<"send messages to server falid!"<<endl;
close(*sockfd); //断开连接,关闭socket套接字
break;
}
}
}
void recvmessage(int *sockfd){
char clientdata[256]={0};
while (1)
{
int len = recv(*sockfd,clientdata,256,0);
if(len>0){
cout<<"<-----get server data is:"<<clientdata<<endl;
}else{
cout<<"don't get server data, please stop pthread!"<<clientdata<<endl;
close(*sockfd);
break;
}
}
}
int main(){
int sockfd=0;
sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
sockaddr_in addr;
addr.sin_port = htons(8000);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int confd = 0;
confd = connect(sockfd,(sockaddr*)&addr, sizeof(addr));
char msg[10]={0};
if(confd==0){
cout<<"sucess connect:"<<sockfd<<endl;
//使用线程的方式处理
pthread_t tid1;
pthread_create(&tid1,0,reinterpret_cast<void *(*)(void *)>(sendmessage),&sockfd);
cout<<"### pthread way*client* sendmessage pthread ID:"<<tid1<<endl;
pthread_detach(tid1);
pthread_join(tid1,0);
pthread_t tid2;
pthread_create(&tid2,0,reinterpret_cast<void *(*)(void *)>(recvmessage),&sockfd);
cout<<"### pthread way*client* recvmessage pthread ID:"<<tid2<<endl;
pthread_detach(tid2);
pthread_join(tid2,0);
}else{
cout<<"file connect"<<endl;
close(sockfd);
return 0;
}
while (1)
{
//不能省略
}
close(sockfd);
return 0;
}
2. epoll+pthread
客户端不变,还是上面的多线程的socket.
为啥不用多线程的服务端,有一个线程切换,系统要的资源相对于select,poll,epoll交多。我也尝试过,但是好像开多个client的时候,server不能直接用线程区分,还是得要个数组去装,后面我再接着试试吧。这还不如就是这些select,poll,epoll方法。
- 现在只是开了一个client端,服务器端给了55518端口。可以看到servier端等待client端发送消息,之后client再接受servier端的发送消息。(这也是不足或者缺点。只有servier等待,client主动,而且收发不能像上面那种随时收发。)
- 现在开始打开第2个client端。可以看到server又开了55536端口。发送消息的模式同上1)。
3)在开了第1个client,第2个client后,我们也机械式的“一问一答”的交流模式 。现在我们再回头去试试第1个client还能不能和server通信,或者说server能不能区分是哪个client.
可以看到,erver可以区分client,但还是一问一答。而且,现在不支持空格的字符串。
4) server端,参考代码
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <iostream>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <string>
#include <sys/epoll.h>
#include <errno.h>
#define MAX_SOCKET 10
using namespace std;
int main(){
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_addr.s_addr=htons(INADDR_ANY);
addr.sin_port = htons(8000);
cout<<"open port:8000"<<endl;
bind(sockfd,(sockaddr*)&addr,sizeof(addr));
listen(sockfd,MAX_SOCKET);//服务端允许多少个来连接
//之前是直接accept接收连接
int epfd = epoll_create(MAX_SOCKET);
struct epoll_event ev,evs[MAX_SOCKET];
ev.data.fd = sockfd;
ev.events = EPOLLIN|EPOLLET;
if(0==epoll_ctl(epfd, EPOLL_CTL_ADD,sockfd,&ev)){
cout<<"+++epoll ctl sucess.++++"<<endl;
}else{
cout<<"epoll ctl fail."<<endl;
}
int n_evCount = 0;
sockaddr_in clientAddr;
socklen_t clientLen = sizeof(clientAddr);
int clientfd,temfd,fd;
char client_ip[INET_ADDRSTRLEN];
char clientdata[256];
int recvlen = 0;
char senddata[256];
int writelen = 0;
while (true)
{
//有循环才可以不断的去监听sockfd,刷新evs数组
n_evCount = epoll_wait(epfd,evs,MAX_SOCKET,500);
for (int i = 0; i < n_evCount; i++)
{
fd = evs[i].data.fd;
if(sockfd == fd){
cout<<"client connect sucess."<<endl;
clientfd = accept(sockfd, (sockaddr*)&clientAddr,&clientLen);
inet_ntop(AF_INET,&clientAddr.sin_addr,client_ip,INET_ADDRSTRLEN);
cout<<"client ip:"<<client_ip<<"port:"<<ntohs(clientAddr.sin_port)<<endl;
//要处理sockfd中的accept事件,就要吧它注册添加过来。
ev.data.fd = clientfd;
ev.events = EPOLLET|EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
}
//sockfd中,产生connect事件,当然是accept接受这个事件。
//遍历事件数组,是不连接事件,是则进行读取操作
if(evs[i].events == EPOLLIN){
cout<<"to ready recv data....."<<endl;
recvlen = read(fd,clientdata,256);
if(recvlen > 0){
cout<<"client data is:"<<clientdata<<endl;
// 注册发送的事件
ev.data.fd = fd;
ev.events = EPOLLET|EPOLLOUT;
//修改注册的事件
epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);
}
}
if(evs[i].events == EPOLLOUT){
cout<<"please put want data:";
cin>>senddata;
writelen = write(fd,senddata,sizeof(senddata));
if(writelen > 0) {
cout<<"send data is:"<<senddata<<endl;
ev.data.fd = fd;
ev.events = EPOLLET|EPOLLIN;
//修改注册的事件
epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);
}
}
}
}
close(sockfd);
return 0;
}
重新学习,进阶学习:
1 重温socket套接字的使用,通常学习的是面向TCP的。 TCP
2 使用进程、线程实现服务器处理多个客户端的连接,TCP
3 使用select、poll、epoll 实现服务器处理多个客户端的连接, TCP
4 使用线程池实现服务器处理多个客户端的连接 TCP
5 使用UDP实现服务器处理多个客户端的连接,(还是socket的函数调用) UDP
6 本地套接字的使用,面向TCP的方式(是进程间通信的一种方式) TCP