文章目录
一、本地环回
IP:127.0.0.1 叫做本地环回。通常用来进行网络通信代码的测试,一般本地跑通说明本地环境以及代码基本没有大问题。
二、将sin_addr设置为INADDR_ANY的含义
INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上进行监听,直到与某个客户建立了链接时才确下来到底用哪个IP地址。
绑定的是INADDR_ANY,则该主机的所有IP层收到的数据都会交给主机,且只需要管理一个套接字。
三、TCP协议
3.1. 监听套接字 listen
由于TCP是有连接的,所以必须将套接字设置为监听状态。
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
socket:创建套接字返回的文件描述符
backlog:底层队列的链接长度,长度是有限制的,不宜设置太大一般分为全链接队列和半链接队列
返回值:成功返回0,失败返回-1
3.2. 建立连接accpet
udp在启动时直接recvfrom读取数据。tcp由于已经建立了连接,所以需要从已完成连接队列中返回一个已完成连接,如果队列为空,则会进入阻塞状态。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd是由socket函数返回的套接字描述符。
addr和addrlen用来返回已连接的客户端的协议地址。
如果我们对客户端的协议地址不感兴趣,可以把arrd和addrlen均置为空指针
返回值:失败返回-1,成功返回一个新的文件描述符
一个服务器通常仅仅创建一个监听套接字(listen_sock),它在该服务器生命周期内一直存在。
accept成功后,内核为每个由服务器进程接收的客户端连接创建一个已连接套接字,accept的返回值是由内核自动生成的该套接字的文件描述符(sock),代表与客户端的TCP连接,后续的接收数据都使用这个sock来进行。当服务器完成对某个给定的客户端的服务器时,相应的已连接套接字就被关闭。
listen_sock和sock的关系有点类似于餐馆接待员和服务员的关系,listen_sock只有一个,accept可以调用多次产生多个sock。
3.3. 连接服务端connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
sockfd是由socket函数返回的套接字描述符。
addr和addrlen用是要连接服务端的协议地址。
返回值:失败返回-1,成功返回0
在通信时,仍然使用我们创建的套接字,也就是sockfd
3.4. 文件和流的概念
一般的文件都是基于流的,比如通过open、fopen、pipe等方式打开的文件都有对应的概念 stream。在c/c++之中打开一个文件都叫做打开一个文件流。
tcp sock也是流的概念,因此读取sock文件时,也可以用read和write,但是常用的是recv。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:accept返回的文件描述符
buf:读数据缓冲区
len:期望读取的数据长度
flags:读数据是IO、不一定有数据让你读,如果读取条件不成立,就挂起等待(默认为0,阻塞等待)
返回值:>0读取到了,<0未读取到,=0表示对端关闭了
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd:accept返回的文件描述符
buf:发送的数据
len:发送的长度
flags:如果发送条件不成立,就挂起等待(默认为0,阻塞等待)
udp中的recvfrom
和sendto
是用于无连接通信,在有连接的tcp通信中,一般采用recv
和send
四、单线程tcp服务端和客户端的实现
4.1. 服务端
tcpServer.hpp:
#pragma once
#include <iostream>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
class tcpServer
{
private:
int port;
int listen_sock;//监听套接字
public:
tcpServer(int _port)
:port(_port)
,listen_sock(-1)
{}
void initServer()
{
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
std::cerr<<"socket fail"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;//填充协议
local.sin_port=htons(port);//填充端口,转换成网网络字节序
local.sin_addr.s_addr=INADDR_ANY;//填写ip
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
{
std::cerr<<"bind error"<<std::endl;
exit(3);
}
if(listen(listen_sock,5)<0)
{
std::cerr<<"listen error"<<std::endl;
exit(4);
}
}
void service(int sock)
{
char buff[200];
while(true)
{
//由于accept已经获取客户端结构体信息,所以直接使用recv读取信息即可
size_t size=recv (sock,buff,sizeof(buff)-1,0);
if(size>0)//读取到数据
{
buff[size]='\0';//末尾添加\0
std::cout<<"client:"<<buff<<std::endl;
//由于已经获取连接,所以在send的时候不需要客户端的sockaddr,直接往sock里发送即可
send(sock,buff,strlen(buff),0);//不需要+1,\0是语言的标准,文件不认识
}
//客户端退出了
else if(size==0)
{
std::cout<<"client quit..."<<std::endl;
close(sock);
break;
}
else
{
std::cout<<"client error..."<<std::endl;
close(sock);
break;
}
}
}
void start()
{
sockaddr_in endpoint;//获取客户端结构体信息,和udp作用一样
while(true)
{
//udp直接通信,tcp第一件事是获取链接
socklen_t len=sizeof(endpoint);
int sock=accept(listen_sock,(struct sockaddr*)&endpoint,&len);
if(sock<0)
{
std::cerr<<"accpet error"<<std::endl;
//获取失败后继续获取
continue;
}
std::string cli_info=inet_ntoa(endpoint.sin_addr);
cli_info+=":";
cli_info+=std::to_string(ntohs(endpoint.sin_port));
std::cout<<"get a new link..."<<cli_info<<std::endl;
service(sock);//调用封装的服务接口
}
}
~tcpServer()
{
close(listen_sock);
}
};
tcpServer.cpp:
#include "tcpServer.hpp"
void Usage(std::string proc)
{
std::cout<<"Usage:"<<proc<<"port"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(1);
}
tcpServer *tp=new tcpServer(atoi(argv[1]));
tp->initServer();//将服务器初始化
tp->start();
delete tp;
return 0;
}
4.2. 客户端
tcpClient.hpp:
#pragma once
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string>
#include<cstring>
class tcpClient{
private:
int svr_port;
std::string svr_ip;
int sock;
public:
tcpClient(std::string _ip="127.0.0.1",int _port=8080)
:svr_port(_port)
,svr_ip(_ip){}
void initClient()
{
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
std::cerr<<"socket error"<<std::endl;
exit(2);
}
//连接,不需要绑定
struct sockaddr_in svr;
svr.sin_family=AF_INET;
svr.sin_port=htons(svr_port);
svr.sin_addr.s_addr=inet_addr(svr_ip.c_str());
if(connect(sock,(struct sockaddr*)&svr,sizeof(svr))!=0)
{
std::cerr<<"connect error"<<std::endl;
}
//connect success;
}
void start()
{
char msg[64];
while(true)
{
std::cout<<"Please Enter Message:";
fflush(stdout);
//获取从键盘输入的数据
size_t s=read(0,msg,sizeof(msg)-1);
if(s>0)
{
msg[s-1]=0;
//发送给服务端
send(sock,msg,strlen(msg),0);
//从服务端接收发回来的数据
size_t ss=recv(sock,msg,sizeof(msg)-1,0);
if(ss>0)
{
msg[ss]=0;
std::cout<<"server echo#"<<msg<<std::endl;
}
else if(ss==0)
{
break;
}
else
{
break;
}
}
}
}
~tcpClient()
{
close(sock);
}
};
tcpClient.cpp:
#include "tcpClient.hpp"
void Usage(std::string proc)
{
std::cout<<"Usage:"<<proc<<"port"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(1);
}
tcpClient *tp=new tcpClient(argv[1],atoi(argv[2]));
tp->initClient();//将服务器初始化
tp->start();
delete tp;
return 0;
}
4.3. 效果展示
五、tcp服务端优化
单进程版本的服务端只有一个进程,当第二个客户端连接时,由于服务端的执行流正在处理第一个客户端的数据,所以会导致第二个客户端无法连接的情况。
5.1. 多进程版本
#pragma once
#include <iostream>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#include<signal.h>
class tcpServer
{
private:
int port;
int listen_sock;//监听套接字
public:
tcpServer(int _port)
:port(_port)
,listen_sock(-1)
{}
void initServer()
{
signal(SIGCHLD,SIG_IGN);//父进程忽略子进程发送过来的信号,即子进程自己清理资源
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
std::cerr<<"socket fail"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;//填充协议
local.sin_port=htons(port);//填充端口,转换成网网络字节序
local.sin_addr.s_addr=INADDR_ANY;//填写ip
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
{
std::cerr<<"bind error"<<std::endl;
exit(3);
}
if(listen(listen_sock,5)<0)
{
std::cerr<<"listen error"<<std::endl;
exit(4);
}
}
void service(int sock)
{
char buff[200];
while(true)
{
//由于accept已经获取客户端结构体信息,所以直接使用recv读取信息即可
size_t size=recv (sock,buff,sizeof(buff)-1,0);
if(size>0)//读取到数据
{
buff[size]='\0';//末尾添加\0
std::cout<<"client:"<<buff<<std::endl;
//由于已经获取连接,所以在send的时候不需要客户端的sockaddr,直接往sock里发送即可
send(sock,buff,strlen(buff),0);//不需要+1,\0是语言的标准,文件不认识
}
//客户端退出了
else if(size==0)
{
std::cout<<"client quit..."<<std::endl;
close(sock);
break;
}
else
{
std::cout<<"client error..."<<std::endl;
close(sock);
break;
}
}
}
void start()
{
sockaddr_in endpoint;//获取客户端结构体信息,和udp作用一样
while(true)
{
//udp直接通信,tcp第一件事是获取链接
socklen_t len=sizeof(endpoint);
int sock=accept(listen_sock,(struct sockaddr*)&endpoint,&len);
if(sock<0)
{
std::cerr<<"accpet error"<<std::endl;
//获取失败后继续获取
continue;
}
std::string cli_info=inet_ntoa(endpoint.sin_addr);
cli_info+=":";
cli_info+=std::to_string(ntohs(endpoint.sin_port));
std::cout<<"get a new link..."<<cli_info<<std::endl;
//创建新进程,让子进程执行服务
pid_t id=fork();
if(id==0)//子进程提供服务
{
//子进程关闭listen_sock不会影响父进程
close(listen_sock);//子进程拷贝了父进程的文件描述表
service(sock);//封装一个服务接口
exit(0);//执行完任务退出
}
//父进程的主要任务是获取链接
//然后链接越多,对应的为服务创建的sock文件描述符也越多
//父进程可用的资源就少了,因此不如关闭
//父进程关闭sock不会影响子进程
close(sock);
//由于signal(SIGCHLD,SIG_IGN); 父进程不必等待子进程
}
}
~tcpServer()
{
close(listen_sock);
}
};
5.2. 多线程版本
多进程的版本创建进程开销还是太大,所以可以改成多线程。
#pragma once
#include <iostream>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
class tcpServer
{
private:
int port;
int listen_sock;//监听套接字
public:
tcpServer(int _port)
:port(_port)
,listen_sock(-1)
{}
void initServer()
{
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
std::cerr<<"socket fail"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;//填充协议
local.sin_port=htons(port);//填充端口,转换成网网络字节序
local.sin_addr.s_addr=INADDR_ANY;//填写ip
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
{
std::cerr<<"bind error"<<std::endl;
exit(3);
}
if(listen(listen_sock,5)<0)
{
std::cerr<<"listen error"<<std::endl;
exit(4);
}
}
static void service(int sock)
{
while(true)
{
char buffer[1024];
size_t s=recv(sock,buffer,sizeof(buffer)-1,0);
if(s>0)
{
buffer[s]=0;
std::cout<<"client#"<<buffer<<std::endl;
send(sock,buffer,strlen(buffer),0);
}
else if(s==0)
{
std::cout<<"client quit..."<<std::endl;
close(sock);
break;
}
else{
std::cout<<"recv client data error..."<<std::endl;
break;
}
}
close(sock);
}
static void* serviceRoutine(void* arg)
{
//线程分离,主线程不等子线程,子线程执行完自己释放
pthread_detach(pthread_self());
std::cout<<"create a new thread for IO"<<std::endl;
int* p=(int*)arg;
int sock=*p;
service(sock);
delete p;
}
void start()
{
sockaddr_in endpoint;//获取客户端结构体信息,和udp作用一样
while(true)
{
//udp直接通信,tcp第一件事是获取链接
socklen_t len=sizeof(endpoint);
int sock=accept(listen_sock,(struct sockaddr*)&endpoint,&len);
if(sock<0)
{
std::cerr<<"accpet error"<<std::endl;
//获取失败后继续获取
continue;
}
std::string cli_info=inet_ntoa(endpoint.sin_addr);
cli_info+=":";
cli_info+=std::to_string(ntohs(endpoint.sin_port));
std::cout<<"get a new link..."<<cli_info<<std::endl;
//创建新进程,让子进程执行服务
pthread_t tid;
int* p=new int(sock);
//传入要执行的函数serviceRoutine和套接字p
pthread_create(&tid,NULL,serviceRoutine,(void*)p);
}
}
~tcpServer()
{
close(listen_sock);
}
};
5.3. 线程池版本
ThreadPool.hpp:
#pragma once
#include <iostream>
#include <queue>
#include<math.h>
#include<unistd.h>
#define NUM 5
class Task{
public:
int sock;
Task(int _sock):sock(_sock){
}
void Run(){
while(true)
{
char buffer[1024];
size_t s=recv(sock,buffer,sizeof(buffer)-1,0);
if(s>0)
{
buffer[s]=0;
std::cout<<"client#"<<buffer<<std::endl;
send(sock,buffer,strlen(buffer),0);
}
else if(s==0)
{
std::cout<<"client quit..."<<std::endl;
close(sock);
break;
}
else{
std::cout<<"recv client data error..."<<std::endl;
break;
}
}
close(sock);
}
~Task(){
std::cout<<"server close sock:"<<sock<<std::endl;
close(sock);
}
};
class ThreadPool{
private:
std::queue<Task*> q;//任务队列
int max_num;//线程的总数
pthread_mutex_t lock;
pthread_cond_t cond;//只能消费者等待,因为生产者要从外部获取任务
void LockQueue(){
pthread_mutex_lock(&lock);
}
void UnlockQueue(){
pthread_mutex_unlock(&lock);
}
bool IsEmpty(){
return q.size()==0;
}
void ThreadWait(){
pthread_cond_wait(&cond,&lock);
}
void ThreadWakeup(){
//唤醒线程
pthread_cond_signal(&cond);
}
void ThreadsWakeup(){
//唤醒所有的线程
pthread_cond_broadcast(&cond);
}
public:
ThreadPool(int _max=NUM):max_num(_max){
}
static void*Routine(void*arg){//必须设置为静态的,因为成员函数会有this形参
ThreadPool*this_p=(ThreadPool*)arg;
while(true){//从任务队列中拿任务
this_p->LockQueue();
while(this_p->IsEmpty()){
this_p->ThreadWait();
}
Task *t;
this_p->Get(&t);
this_p->UnlockQueue();
t->Run();
delete t;
}
}
void ThreadPoolInit(){
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&cond,NULL);
pthread_t t;//创建一批线程
for(int i=0;i<max_num;i++){
pthread_create(&t,NULL,Routine,this);
}
}
void Put(Task &in){//往任务队列中放任务
LockQueue();
q.push(&in);
UnlockQueue();
ThreadsWakeup();
}
void Get(Task **out){
Task*t=q.front();
q.pop();
*out=t;
}
~ThreadPool(){
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
tcpServer.hpp:
#pragma once
#include<iostream>
#include<string>
#include<cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>
#include"ThreadPool.hpp"
//全链接队列长度
#define BACKLOG 5
class tcpServer{
private:
int port;//端口号
int listen_sock;//监听套接字
ThreadPool *tp;//线程池
public:
tcpServer(int _port)
:port(_port)
,listen_sock(-1)
,tp(NULL)
{}
void initServer()
{
signal(SIGCHLD,SIG_IGN);
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"bind error"<<std::endl;
exit(3);
}
//套接字必须设定为监听状态
if(listen(listen_sock,BACKLOG)<0)
{
std::cerr<<"bind error"<<std::endl;
exit(4);
}
//创建并初始化线程池
tp=new ThreadPool();
tp->ThreadPoolInit();
}
void start()
{
sockaddr_in endpoint;
while(true)
{
socklen_t len=sizeof(endpoint);
int sock=accept(listen_sock,(struct sockaddr*)&endpoint,&len);
if(sock<0)
{
std::cerr<<"accept error"<<std::endl;
continue;
}
std::string cli_info=inet_ntoa(endpoint.sin_addr);
cli_info+=":";
cli_info+=std::to_string(ntohs(endpoint.sin_port));
std::cout<<"get a new link..."<<cli_info<<std::endl;
Task* t=new Task(sock);
tp->Put(*t);
}
}
~tcpServer()
{}
};
5.4. 各个版本特点
- 单进程版本:只有一个执行流,一般不使用,多进程/多线程版本通常是小型的应用(局域网,少量机)。
- 多进程版本:进程之间是独立的,健壮性强,但是比较吃资源,效率低下
- 多线程版本:健壮性不强,较吃资源,效率相对较高。如果有大量的客户端,则系统会存在大量的执行流,执行流之间的切换会使效率低下。
- 线程池版本:可以减轻一大批客户端来进行链接时,服务器的压力。同时、由于线程池的线程数量有限,可以有效防止外部非法请求攻击、也在一定程度上保护了计算机
六、将一个进程放置后台,拿到前台
- &
这个用在一个命令的最后,可以把这个命令放到后台执行 - ctrl+ z
可以将一个正在前台执行的命令放到后台,并且暂停 - jobs
查看当前有多少在后台运行的命令 - fg
将后台中的命令调至前台继续运行 - bg
将一个在后台暂停的命令,变成继续执行
七、基于tcp的简单的多人聊天
7.1. 服务端
ThreadPool.hpp:
#pragma once
#include <iostream>
#include <queue>
#include<math.h>
#include<unistd.h>
using namespace std;
#define NUM 10
class Task{
public:
int sock;
Task(int _sock):sock(_sock){
}
void Run(){
while(true)
{
//接收到发来的数据,然后发给除了自己的所有人
char buffer[1024];
size_t s=recv(sock,buffer,sizeof(buffer)-1,0);
if(s>0)
{
buffer[s]=0;
string sendStr(buffer);
sendStr.insert(0,userInfMap[sock]+">");
int sendSock;
for(auto e:userInfMap)
{
sendSock=e.first;
if(sendSock!=sock)
{
send(sendSock,sendStr.c_str(),sendStr.size(),0);
}
}
}
else if(s==0)
{
std::cout<<userInfMap[sock]<<"退出..."<<std::endl;
close(sock);
break;
}
else{
std::cout<<"recv"<<userInfMap[sock]<<"data error..."<<std::endl;
break;
}
}
close(sock);
}
~Task(){
std::cout<<"server close sock:"<<sock<<std::endl;
close(sock);
}
};
class ThreadPool{
private:
std::queue<Task*> q;//任务队列
int max_num;//线程的总数
pthread_mutex_t lock;
pthread_cond_t cond;//只能消费者等待,因为生产者要从外部获取任务
void LockQueue(){
pthread_mutex_lock(&lock);
}
void UnlockQueue(){
pthread_mutex_unlock(&lock);
}
bool IsEmpty(){
return q.size()==0;
}
void ThreadWait(){
pthread_cond_wait(&cond,&lock);
}
void ThreadWakeup(){
//唤醒线程
pthread_cond_signal(&cond);
}
void ThreadsWakeup(){
//唤醒所有的线程
pthread_cond_broadcast(&cond);
}
public:
ThreadPool(int _max=NUM):max_num(_max){
}
static void*Routine(void*arg){//必须设置为静态的,因为成员函数会有this形参
ThreadPool*this_p=(ThreadPool*)arg;
while(true){//从任务队列中拿任务
this_p->LockQueue();
while(this_p->IsEmpty()){
this_p->ThreadWait();
}
Task *t;
this_p->Get(&t);
this_p->UnlockQueue();
t->Run();
delete t;
}
}
void ThreadPoolInit(){
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&cond,NULL);
pthread_t t;//创建一批线程
for(int i=0;i<max_num;i++){
pthread_create(&t,NULL,Routine,this);
}
}
void Put(Task &in){//往任务队列中放任务
LockQueue();
q.push(&in);
UnlockQueue();
ThreadsWakeup();
}
void Get(Task **out){
Task*t=q.front();
q.pop();
*out=t;
}
~ThreadPool(){
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
tcpServer.hpp:
#pragma once
#include<iostream>
#include<string>
#include<cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>
#include<map>
using namespace std;
map<int,string> userInfMap;
#include"ThreadPool.hpp"
//全链接队列长度
#define BACKLOG 5
class tcpServer{
private:
int port;//端口号
int listen_sock;//监听套接字
ThreadPool *tp;//线程池
public:
tcpServer(int _port)
:port(_port)
,listen_sock(-1)
,tp(NULL)
{}
void initServer()
{
signal(SIGCHLD,SIG_IGN);
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"bind error"<<std::endl;
exit(3);
}
//套接字必须设定为监听状态
if(listen(listen_sock,BACKLOG)<0)
{
std::cerr<<"bind error"<<std::endl;
exit(4);
}
//创建并初始化线程池
tp=new ThreadPool();
tp->ThreadPoolInit();
}
void start()
{
sockaddr_in endpoint;
while(true)
{
socklen_t len=sizeof(endpoint);
int sock=accept(listen_sock,(struct sockaddr*)&endpoint,&len);
if(sock<0)
{
std::cerr<<"accept error"<<std::endl;
continue;
}
char userName[64]={'\0'};
size_t s=recv(sock,userName,sizeof(userName)-1,0);
string cli_info(userName);
userInfMap[sock]=cli_info;
std::cout<<cli_info<<"已连接"<<std::endl;
Task* t=new Task(sock);
tp->Put(*t);
}
}
~tcpServer()
{}
};
tcpServer.cpp:
#include "tcpServer.hpp"
void Usage(std::string proc)
{
std::cout<<"Usage:"<<proc<<"port"<<std::endl;
}
int main(int argc,char* argv[])
{
// if(argc!=2)
// {
// Usage(argv[0]);
// exit(1);
// }
tcpServer *tp=new tcpServer(atoi(argv[1]));
tp->initServer();//将服务器初始化
tp->start();
delete tp;
return 0;
}
7.2. 客户端
tcpClient.hpp:
#pragma once
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string>
#include<cstring>
#include<pthread.h>
using namespace std;
class tcpClient{
private:
int svr_port;
std::string svr_ip;
int sock;
char userName[100];
public:
tcpClient(std::string _ip="127.0.0.1",int _port=8080)
:svr_port(_port)
,svr_ip(_ip){}
void initClient()
{
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
std::cerr<<"socket error"<<std::endl;
exit(2);
}
//连接,不需要绑定
struct sockaddr_in svr;
svr.sin_family=AF_INET;
svr.sin_port=htons(svr_port);
svr.sin_addr.s_addr=inet_addr(svr_ip.c_str());
if(connect(sock,(struct sockaddr*)&svr,sizeof(svr))!=0)
{
std::cerr<<"connect error"<<std::endl;
}
//connect success;
cout<<"请输入你的用户名:";
fflush(stdout);
size_t s=read(0,userName,sizeof(userName)-1);
userName[s-1]=0;
send(sock,userName,strlen(userName),0);
}
static void readService(int sock)
{
char buff[200];
while(true)
{
size_t size=recv (sock,buff,sizeof(buff)-1,0);
if(size>0)//读取到了
{
buff[size]='\0';//末尾添加\0
cout<<buff<<endl;
}
else if(size==0)
{
cout<<"服务器关闭..."<<endl;
close(sock);//关闭对应的套接字
break;
}
else//读取错误
{
cerr<<"read error"<<endl;
close(sock);
break;
}
}
}
static void *sevrun(void *arg)//只能有一个参数,因此必须是static
{
pthread_detach(pthread_self());//线程分离,不用再进行等待
int *p=(int*)arg;
int sock=*p;
readService(sock);//执行任务
delete p;
return NULL;
}
void start()
{
//创建子线程去读服务端发来的数据
pthread_t tid;
int *p=new int(sock);
pthread_create(&tid,NULL,sevrun,(void*)p);
char msg[64];
std::cout<<"请在下面输入内容>"<<endl;;
while(true)
{
fflush(stdout);
size_t s=read(0,msg,sizeof(msg)-1);
if(s>0)
{
msg[s-1]=0;
send(sock,msg,strlen(msg),0);
}
}
}
~tcpClient()
{
close(sock);
}
};
tcpClient.cpp:
#include "tcpClient.hpp"
void Usage(std::string proc)
{
std::cout<<"Usage:"<<proc<<"port"<<std::endl;
}
int main(int argc,char* argv[])
{
// if(argc!=3)
// {
// Usage(argv[0]);
// exit(1);
// }
tcpClient *tp=new tcpClient("49.232.92.201",atoi(argv[1]));
tp->initClient();//将服务器初始化
tp->start();
delete tp;
return 0;
}