目录
一. 套接字基本概念
所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象,一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制
IP地址
IP地址是在IP协议中,主要功能是用来标识网络上不同主机的地址。
IP地址是由32位组成,主要由三部分:地址类别,网络号和主机号:
TCP和UDP协议
TCP/IP协议传输层使用最广泛的两个协议分别是TCP协议和UDP协议,UDP套接口是数据报套接字的一种,而TCP套接口是字节流套接字的一种。
TCP/IP协议传输层的 主要任务是向位于不同的(有时候位于同一主机)上的应用程序提供端到端的通信服务,为了区分应用程序,TCP和UDP引入了端口号的概念,端口本质是一个16位的整数
端口号
端口号(port)是传输层协议的内容
- 是一个2字节的16位整数。
- 端口号是用来表示进程的,告诉操作系统,当前的数据要交给哪一个进程处理
- ip地址+端口号能够标识网络上一台主机上的一个进程。
- 一个端口号只能标识一个进程
任何的网络服务与网络客户端,如果要进行正常的数据通信,必须使用端口号,来唯一标识进程。
公网ip:唯一的标识全网唯一的一台主机
socket通信,本质是进程间通信,跨网络的进程进通信。
端口号vs 进程pid
而进程pid就好比进程的身份证号,而端口号就好比学号,在一台机器上,假设有50个进程在运行,但是只有5个进程进行网络通信,这时候我们只要给这5个进程分配端口号即可。
网络字节序
由于不同计算机系统采用不同的字节序存储数据,同样一个4字节的32位整数在内存中存储的方式是不同的,这称为本地字节序。字节序列分为大端字节序和小端字节序,Intel处理器大多使用大端字节序,Motoro大多使用小端字节序。小端字节序是指低位字节存放内存的低地址处,大端字节序指的是高位字节存储在内存的低地址处。
如果进程只在单机环境下运行,并且不和其他进程打交道,我们完全可以忽略字节序的存在,但是,如果进程需要跟其他计算机上的进程进行交换,我们必须考虑字节序的问题。
1的字节序分别在大端机器和小端机器存储情况,如果大端机器将内存中的数据通过网络传给小端机器,那么小端机器从网络获得的数据放进内存中的数据是相反的。这就是网络字节序问题。
TCP/IP协议进行网络数据传输时,规定一种数据表示格式,它与具体的cpu和操作系统无关,保证了数据在不同主机传输时能够被正确解释,这就是网络字节序,网络字节序统一采用大端字节序。
因此,当两台机器进行通信时,必须先将本地字节序转换为网络字节序转化为网络字节序在进行发送,当收到数据之后,应当将网络字节序转化为本地字节序再进行后续使用。
本地字节序转换成网络字节序
#include <arpa/inet.h>
//将32位的整型数据从本地转换为网络字节序
uint32_t htonl(uint32_t hostlong);
//将16位的整形数据从本地转换为网络字节序
uint16_t htons(uint16_t hostshort);
网络字节序转换为本地字节序
#include <arpa/inet.h>
//将32位的整形网络序列转换为本地序列
uint32_t ntohl(uint32_t netlong);
//将16位的整形网络序列转换为本地序列
uint16_t ntohs(uint16_t netshort);
二. 套接字的基本操作
socket的创建
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
int socket(int domain, int type, int protocol);
域(domain)
域指定了Socket编成模型的地址族,其类型为int,常见的取值如下表所示:
网络编程中最常用的socket域取值是AF_INET和AF_INET6协议,其中AF_INET6协议是下一代互联网的协议,客服目前IPv4存在可用地址有限的问题,但AF_INET6协议还没有被实际运用,在写代码时一般使用AF_INET.
类型(type)
类型主要指定了通信双方的数据传输格式,比较常见的数据格式类型有三种:
协议(Protcol)
协议主要指通信双方的约定,如TCP协议和UDP协议,但正常情况下,当双方的域(domain)和通信数据类型(type)确定以后,协议就会被唯一确定l,传0表示根据domain和type类型推出协议。所以该参数一般传0即可。
返回值
函数socket()的返回值是一个文件描述符。套接字的建立本质是一个进程在内存中打开一个文件,并且这个文件中的数据与网卡互相连接,然后将打开的文件描述符返回给进程,然后进程可以对这个文件可以进行读写数据。
成功返回一个文件描述符(socket描述符),失败返回-1,同时errno被设置成相应的值。
创建socket本质是一个进程在内存中打开一个文件缓冲区,然后这个文件缓冲区与网卡进行数据交互。
struct socketaddr地址结构
在各种底层网络协议中,如IPv4和IPv6,以及UNIX DOMAIN socket,这些底层网络协议地址格式不同,所以为了兼容这些底层网络协议,Socket API定义了一个通用的struct socketaddr,这使得不同的地址结构可以被bind(),connect,revfrom,sendto()等函数调用。
struct sockaddr 结构
struct sockaddr {
sa_family_t sa_family; //地址族 16位
char sa_data[14]; //14个字节,包括目标地址和端口号
}
struct sockaddr_in 结构
struct sockaddr_in {
__kernel_sa_family_t sin_family; // 地址族 16位
__be16 sin_port; //端口号,16位,可以看成一个int类型
struct in_addr sin_addr; //ip地址 32位
//填充信息,一般不需要管
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
//以整数的形式指定套接字的网络地址
struct in_addr {
__be32 s_addr; //存放32位ip地址
};
IPv4地址使用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位ip地址。
IPv4和IPv6的地址格式定义在netinet/in.h头文件 中,IPv4地址用 sockaddr_in 结构体表示 , 包括 16 位地址类型 , 16 位端口号和32 位 IP地址.
socket绑定地址(bind函数)
若要使socket也可以被其他进程使用,服务器必须给socket绑定ip地址和端口号。(服务器是固定的)
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数sockfd为待绑定的socket描述符
参数addr是本地sockaddr地址结构指针
addrlen为本地sockaddr地址结构的大小
执行成功返回0,失败返回-1
对于绑定IP4套接字,我们不会直接去创建一个struct sockaddr变量,而是直接定义一个struct sockaddr_in变量,然后将ip地址和端口号填充进struct sockaddr_in变量中,最后在传参的时候将struct sockaddr_in*变量强制转换为struct sockaddr*类型,其他套接字也是一样。
绑定IP4套接字过程如下:
struct sockaddr_in s;
memset(&s,'\n',sizeof(s));
s.sin_family = AF_INET;
s.sin_port = htons(_port);
s.sin_addr.s_addr = INADDR_ANY;
创建struct soaddr_in对象时,首先需要用bzero()函数或memset()函数将其置为0.
在调用bind()函数时一般不要将端口号设置为小于1024的值,因为1~1024是保留端口号,用户可以选择大于1024中任何一个没有被占用的端口号。
在使用bind()函数时,需要将sin_port转换为网络字节序,而sin_addr则不需要转换。
在主机上定义的IP地址一般是“ 112.233.111.111”这种点分式字符串IP,但我们需要将这种字符串IP转换为整数IP填充进in_addr对象中的s_addr中。IP转换函数如下:
//将字符型IP转换为32位整数型ip
in_addr_t inet_addr(const char *cp);
//将网络地址转换为字符串
char *inet_ntoa(struct in_addr in)
inet_addr常用在客户端转换。服务器原因如下:
IP地址也可以不用直接填充,使用INADDR_ANY变量,可以直接填入本机IP地址,推荐使用这种方法。在我们的云服务器中,一般使用这种操作填充IP地址,因为云服务器的IP是由云厂商提供的,这个云服务器IP不能直接被绑定。
s.sin_addr.s_addr = INADDR_ANY;
一般只有服务器才会去绑定IP地址和端口号 ,客户端不需要绑定IP地址和端口号。因为服务器需要不断的监听客户端的请求,服务器的IP地址和端口号它不可以一直改变,而客户端在连接服务器或者给服务器发送数据时,系统会给客户端随机分配一个端口,这个端口号和ip地址会发送给服务器,接下来服务器根据ip地址和端口号就可以给客户回应消息。
Socket监听连接(listen函数)
在成功建立Socket并完成与本地地址绑定后。使用listen()函数来监听客户的连接请求。调用函数listen()函数会创建一个等待队列,在其中存放未处理的客户端请求,其函数原型如下:
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
int listen(int sockfd, int backlog);
参数为Socket描述符
参数为backlog为请求队列中允许的最大请求数,系统默认为5
函数listen()执行成功返回0若执行失败返回-1.同时errno被设置成相应的值。
Socket请求连接(connect函数)
cp协议中,函数connect()用于客户端向服务器发起连接请求;UDP协议是面向无连接的,因此无需使用connect(),其函数原型如下:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd为待绑定Socket的描述符
参数addr是服务器Socket地址结构指针
参数addrlen为addr的大小。
函数执行成功返回0,失败返回-1,同时errno被设置成相应的值。
Socket接受连接(accept函数)
服务器进程调用函数listen()创建等待队列之后,调用accept()函数等待并接受客户端的连接请求,函数accept通常从连接等待队列中取出一个未处理的连接请求,其函数
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数sockfd为Socket的描述符
参数addr为存放客户端Sock地址结构指针
参数addrlen为addr的大小
函数accept()执行成功,返回客户端新的套接字描述符;若执行失败,返回-1,同时errno被设置成相应的值。
Socket接受数据 (recvfrom函数)
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数sockfd是Socket描述符
buf指定接受数据存放的缓冲区
len用于指定接受数据的大小
flags用于指定接受数据的标志,一般设置为0.(//sockfd被MSG_DONTWAIT设置成非阻塞式)
函数recvfrom中,参数src_addr用于存放数据发送方的网络地址结构(ip4是由sockaddr_in填充对象强转为sockaddr类型),参数addrlen是src_addr的大小。
recv和recvfrom执行成功,返回实际接受的字节数,执行失败,返回-1.
Socket发送数据(sendto函数)
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd是Socket描述符
参数buf用于指定发送数据的缓冲区
参数len是指定发送数据的大小
参数flags用于指定发送数据标志,一般设置为0.
函数sendto(),dest_addr用于指定接受方的网络地址结构(ip4是由sockaddr_in填充对象强转为sockaddr类型),addrlen是dest_addr的大小。
函数send()和sendto()发送数据成功,返回实际发送出数据的大小,失败返回-1.
Socket关闭
因为socketfd是文件描述符,所以用户也可以用close()函数来终止服务器与客户端的套接字的连接。
#include <unistd.h>
int close(int fd);
close函数执行成功,返回0,失败返回-1.
三. UDP编程模型
UDP编程是不需要连接的,它是面向数据报。UDP socket是不可靠的,报文可能会丢失,重复或达到的顺序与它发送时的顺序不同;第二,当UDP上的socket填满的数据时,再发送数据时则报文会丢弃。
代码如下:
server.hpp文件
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
using namespace std;
class server
{
private:
int _port;
int _fd;
string _ip;
public:
server(string ip, int port)
: _ip(ip), _port(port)
{
}
bool initUdpServer()
{
// 创建
_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (_fd < 0)
{
cout << "socket fali\n"
<< endl;
return false;
}
cout << "socket success" << endl;
// 绑定
struct sockaddr_in s;
s.sin_family = AF_INET;
// htons是将整型变量从主机字节顺序转变成网络字节顺序
s.sin_port = htons(_port);
s.sin_addr.s_addr = 0;
if (bind(_fd, (struct sockaddr *)&s, sizeof(s)) < 0)
{
cerr << "bind error" << endl;
return false;
}
cout << "bind success" << endl;
return true;
}
void Start()
{
for (;;)
{
#define NUM 32
char buffer[NUM];
struct sockaddr_in peer;
socklen_t len=0;
ssize_t sz=recvfrom(_fd,(void*)buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(sz>0)
{
buffer[sz]='\0';
int port=ntohs(peer.sin_port);
//ip字节序转换
string s=inet_ntoa(peer.sin_addr);
cout<<s<<":"<< _port<<"#"<<buffer<<endl;
}
else
{
cerr <<"recvfrom error"<<endl;
}
}
}
};
server.cc文件
#include"myUDP.hpp"
#include<stdlib.h>
int main(int argc,char*argv[])
{
if(argc!=2)
{
cout << "Usage error"<< argv[0]<< "need port"<<endl;
}
string ip="127.0.1";//127.0.1表示本地环回;
int port=atoi(argv[1]);
server*s=new server(ip,port);
s->initUdpServer();
s->Start();
return 0;
}
client.hpp文件
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
using namespace std;
class client
{
private:
int _sockfd;
int _sev_port;
string _sev_ip;
public:
client(string sev_ip, int sev_port)
: _sev_ip(sev_ip), _sev_port(sev_port)
{
}
bool InitClient()
{
_sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd<0)
{
cout <<"socket error"<<endl;
return false;
}
return true;
}
void start()
{
struct sockaddr_in dest;
//重置
memset(&dest,'0',sizeof(dest));
dest.sin_family=AF_INET;
dest.sin_port=htons(_sev_port);
//ip字节序转换
dest.sin_addr.s_addr=inet_addr(_sev_ip.c_str());
string buffer;
for(;;)
{
cout << "Plerse Enter"<< endl;
cin >> buffer;
sendto(_sockfd,(void*)buffer.c_str(),(size_t)buffer.size(),0,(struct sockaddr*)&dest,sizeof(dest));
}
}
};
client.cc文件
#include"myUDPclient.hpp"
//./client "127.0.0.1" 8081
int main(int argc,char* argv[])
{
if(argc!=3)
{
cout<<"Usuage"<<"server_ip server_port"<<endl;
}
char* ip=argv[1];
int port=atoi(argv[2]);
client* c=new client(ip,port);
c->InitClient();
c->start();
return 0;
}
四. Tcp编程模型
Tcp协议在两个端点(即应用程序)之间提供了可靠的,面向连接的,双向字节流的通信管道。
cp协议提供了客户端和服务器之间的连接。Tcp客户端首先给某个定服务器建立一个连接,然后再通过连接与服务器进行数据交换,最后终止这个连接。
Tcp协议保证了数据传输的可靠性,当Tcp一端向另一端发送数据时,它要求另一端返回一个“确认”,如果没有收到“确认”,Tcp就会自动多次传送数据,在数次重传失败后才会放弃。
Tcp协议提供的连接是全双工的,这意味着在一个给定的连接上,一个Tcp端点可以在同一时刻发送数据又接受数据。
Tcp协议
- 传输层协议
- 有链接
- 可靠传输
- 面向字节流
Tcp服务器模型
Tcp服务器和客户端接收和发送数据可以用read和write,首先,前面socket描述符本质是文件描述符,所以可以用文件的操作接口, 其次,服务器在accept()后连接客户端就可以拿到客户端的IP和端口号信息。所以服务器和客户端接收数据和发送数据正常使用read()函数和write()函数。
read()如果大于0,说明读到了信息。如果等于0,说明对端已关闭。
makefile文件
.PHONY:all
all:client server
client:client.cc
g++ $^ -o $@
server:server.cc
g++ $^ -o $@
clean:
rm -f client server
服务器头文件
下面是单线程单进程的服务器,一次只能连接一个客户端。
#pragma once
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string>
#include <stdlib.h>
using namespace std;
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)
{
cerr << "sock false" << endl;
return;
}
cout << "sock success" << endl;
struct sockaddr_in s;
memset(&s,'\n',sizeof(s));
s.sin_family = AF_INET;
s.sin_port = htons(_port);
s.sin_addr.s_addr = INADDR_ANY;
// 绑定套接字
if (bind(listen_sock, (struct sockaddr *)&s, sizeof(s)) < 0)
{
cerr << "bind error" << endl;
return;
}
// 让套接字去监听连接
if (listen(listen_sock, 5) < 0)
{
cerr << "listen false" << endl;
}
}
void Loop()
{
struct sockaddr_in sockaddr;
for (;;)
{
// 接受监听套接字其中的一个连接,然后在创建新的套接字给这个连接
// 新的套接字进行数据通信
socklen_t len = sizeof(sockaddr);
int sockfd = accept(listen_sock, (struct sockaddr *)&sockaddr, &len);
if (sockfd < 0)
{
cerr << "accept false" << endl;
}
// ip字节序转换,转换成网络
string ip = inet_ntoa(sockaddr.sin_addr);
int port = ntohs(sockaddr.sin_port);
cout << "get new link..[" << ip << "]" << port << endl;
Server(sockfd, ip, port);
}
}
void Server(int sock, string ip, int port) // 服务
{
char buffer[1024];
while (true)
{
// 接受数据
ssize_t size = read(sock, &buffer, sizeof(buffer));
if (size > 0)
{
buffer[size] = '\0';
cout << ip << ":" << port << "#" << buffer << endl;
}
else if (size == 0)
{
cout << ip << ":" << port << "close" << endl;
break;
}
else
{
cout << "port error!!!!"<<endl;
}
}
close(sock);
}
};
服务器.cc文件
#include"server.hpp"
int main(int argc,char* argv[])
{
if(argc!=2)
{
cout <<"Usage:server port"<<endl;
exit(-1);
}
TcpServer*ts=new TcpServer(atoi(argv[1]));//创建服务器
ts->InitServer();//初始化服务器
ts->Loop();//启动服务器
return 0;
}
客户端头文件
#pragma once
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
using namespace std;
class TcpClient
{
private:
int _port;
int sockfd;
string _ip;
public:
TcpClient(string ip, int port)
: _ip(ip), _port(port), sockfd(-1)
{
}
void InitTcpClient()
{
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
cerr << "socker false" << endl;
return;
}
}
void Start()
{
struct sockaddr_in peer;
// 清空
memset(&peer, '\0', sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(_port);
peer.sin_addr.s_addr = inet_addr(_ip.c_str());
// 本地套接字链接服务器
if (connect(sockfd, (struct sockaddr *)&peer, sizeof(peer)) == 0)
{
// 连接成功
cout << "connect success" << endl;
Request(sockfd);
}
else
{
cerr << "connect false" << endl;
}
}
// 请求任务接口
void Request(int sockfd)
{
string message;
char buffer[1024];
while(true)
{
cout <<"Please Enter#";
cin >> message;
//往套接字中输入数据
write(sockfd,message.c_str(),message.size());
ssize_t size=read(sockfd,buffer,sizeof(buffer));
if(size >0)
{
buffer[size]='\0';
cout << "server#"<<buffer<<endl;
}
}
}
~TcpClient()
{
if(sockfd>0)
{
close(sockfd);
}
}
};
客户端.cc文件
#include"client.hpp"
#include<stdlib.h>
// 启动客户端的方式:./client ip port
int main(int argv,char* argc[])
{
if(argv!=3)
{
cout<<"Usage : ./client ip port"<<endl;
exit(-1);
}
TcpClient* tc=new TcpClient(argc[1],atoi(argc[2]));
tc->InitTcpClient();
tc->Start();//开始运行服务端
return 0;
}
运行结果:
因为这是单线程单进程的服务器,所以服务器每次只能接受一个客户端的连接。
多进程服务器
多进程服务器只需要在server.hpp文件改Loop()函数,其他文件不用改。
主线程是爷爷进程,爷爷进程不断的去等待队列中接受新连接,接受到一个新连接,就去创建一个爸爸进程,再创建一个儿子进程去服务客户端的连接请求,再退出爸爸进程,如果有多个连接,爷爷进程就会创建多个儿子进程去服务多个连接。为什么要创建儿子进程?如果只创建爸爸进程,那么爸爸进程再退出前,爷爷进程需要去回收爸爸进程的资源,爷爷进程就会阻塞等待,如果再创建一个儿子进程,再退出爸爸进程,那么儿子进程就会被操作系统给“领养",当儿子系统退出时,操作系统会自动释放儿子进程的资源,此时爷爷进程就不会被阻塞,继续去接收新的连接。
void Loop()
{
struct sockaddr_in sockaddr;
for (;;)
{
// 接受监听套接字其中的一个连接,然后在创建新的套接字给这个连接
// 新的套接字进行数据通信
socklen_t len = sizeof(sockaddr);
int sockfd = accept(listen_sock, (struct sockaddr *)&sockaddr, &len);
if (sockfd < 0)
{
cerr << "accept false" << endl;
}
// ip字节序转换,转换成网络
string ip = inet_ntoa(sockaddr.sin_addr);
int port = ntohs(sockaddr.sin_port);
cout << "get new link..[" << ip << "]" << port << endl;
pid_t id=fork();
if(id==0)
{
close(listen_sock);//关掉父亲进程的监听套接字
if(fork()>0)
{
exit(-1);
}
Server(sockfd, ip, port);
}
close(sockfd);//关掉爷爷进程的套接字
waitpid(-1,nullptr,0);
}
}
当创建儿子进程去服务新链接后,爷爷进程就需要关闭新连接的sock描述符,防止爷爷进程的文件描述符表泄漏。
多线程服务器
#pragma once
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
using namespace std;
class Pragma
{
public:
int sockfd;
std::string ip;
int port;
public:
Pragma(int _sockfd, std::string _ip, int _port)
: sockfd(_sockfd), ip(_ip), port(_port)
{
}
};
class TcpServer
{
private:
int port;
int listen_sock;
public:
TcpServer(int _port)
: port(_port), listen_sock(-1)
{
}
void InitTcpServer()
{
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
{
std::cerr << "socket failing" << std::endl;
exit(2);
}
struct sockaddr_in lock;
lock.sin_family = AF_INET;
lock.sin_port = htons(port);
lock.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_sock, (struct sockaddr *)&lock, sizeof(lock)) < 0)
{
std::cerr << "bind failing" << std::endl;
exit(2);
}
if (listen(listen_sock, 5) < 0)
{
std::cerr << "listen failing" << std::endl;
exit(3);
}
std::cout << "listen success....." << std::endl;
}
void Loop()
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
for (;;)
{
int sockfd = accept(listen_sock, (struct sockaddr *)&peer, &len);
if (sockfd < 0)
{
continue;
}
std::string ip = inet_ntoa(peer.sin_addr);
int port = ntohs(peer.sin_port);
std::cout << "get new link [" << ip << "] :" << port << std::endl;
pthread_t id;
Pragma*p=new Pragma(sockfd,ip,port);
pthread_create(&id,NULL,Routine,p);
}
}
static void *Routine(void *arg)
{
Pragma*p=(Pragma*)arg;
pthread_detach(pthread_self());
Server(p->sockfd,p->ip,p->port);
close(p->sockfd);
delete p;
}
static void Server(int sockfd, std::string ip, int port)
{
char buffer[1024];
while (true)
{
buffer[0] = '0';
int sz = read(sockfd, buffer, sizeof(buffer) - 1);
if (sz > 0)
{
buffer[sz] = '\0';
std::cout << ip << ":" << port << "#" << buffer << std::endl;
}
else if (sz == 0)
{
std::cerr << "client close" << std::endl;
break;
}
else
{
break;
}
}
close(sockfd);
}
~TcpServer()
{
if (listen_sock > 0)
close(listen_sock);
}
};
多线程服务器,当主线程接受连接多少个客户端时,主线程会创建相应的子线程去服务客户端,同时主线程和客户端不能随便关闭socket描述符,因为主线程和子线程使用的同一文件描述符表。
线程池TCP实现
调用的仿函数Task.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
using namespace std;
class Task
{
public:
Task()
{
}
void operator()(int sockfd,string ip,int port)
{
char buffer[1024];
while(true)
{
int sz=read(sockfd,buffer,sizeof(buffer));
if(sz>0)
{
buffer[sz]='\0';
cout<< ip<<":"<<port<<":"<<buffer<<endl;
}
else if(sz==0)
{
cerr <<"client close"<<endl;
break;
}
else{
break;
}
}
close(sockfd);
}
};
threadpool.hpp头文件(线程池)
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#define NUM 2 // 线程池大小
template <class T>
class ThreadPool
{
private:
queue<T> q; // 任务队列
int thread_num; // 线程池的线程数量
pthread_mutex_t lock; // 互斥锁
pthread_con_t cond; // 条件变量,唤醒线程
public:
ThreadPool(int num = NUM)
: thread_num(num);
{
pthread_mutex_init(&lock, NULL);
pthread_con_init(&cond, NULL);
}
bool Empty() // 判断队列是否为空
{
return q.size() == 0 ? true : false;
}
static void *Routine(void *arg) // 线程执行流
{
pthread_detach(pthread_self());
ThreadPool *p = (ThreadPool *)arg;
while (1)
{
p->LockQueue();
while (p->Empty())
{
p->Wait(); // 为空就睡觉
}
T data;
p->Pop(data);
p->UnLockQueue();
cout << pthread_self() << "# ";
// 处理任务
data.Run();
// 任务仿函数
sleep(1);
}
}
void ThreadPoolInit()
{
pthread_t tid;
for (int i = 0; i < thread_num; i++)
{
pthread_create(&tid, NULL, Routine, (void *)this);
}
}
void Wait() // 睡觉
{
pthread_cond_wait(&cond, &lock);
}
void LockQueue()
{
pthread_mutex_lock(&lock);
}
void UnLockQueue()
{
pthread_mutex_unlock(&lock);
}
void Push(const T &in)
{
LockQueue();
q.push(in);
UnLockQueue();
SignalThread();
}
void SignalThread() // 唤醒
{
pthread_cond_signal(&cond);
}
void Pop(T &out) // 取出任务再删除
{
out = q.front();
q.pop();
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
poolserver.hpp
#pragma once
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
using namespace std;
class Pragma
{
public:
int sockfd;
std::string ip;
int port;
public:
Pragma(int _sockfd, std::string _ip, int _port)
: sockfd(_sockfd), ip(_ip), port(_port)
{
}
};
class TcpServer
{
private:
int port;
int listen_sock;
public:
TcpServer(int _port)
: port(_port), listen_sock(-1)
{
}
void InitTcpServer()
{
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
{
std::cerr << "socket failing" << std::endl;
exit(2);
}
struct sockaddr_in lock;
lock.sin_family = AF_INET;
lock.sin_port = htons(port);
lock.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_sock, (struct sockaddr *)&lock, sizeof(lock)) < 0)
{
std::cerr << "bind failing" << std::endl;
exit(2);
}
if (listen(listen_sock, 5) < 0)
{
std::cerr << "listen failing" << std::endl;
exit(3);
}
std::cout << "listen success....." << std::endl;
}
void Loop()
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
for (;;)
{
int sockfd = accept(listen_sock, (struct sockaddr *)&peer, &len);
if (sockfd < 0)
{
continue;
}
std::string ip = inet_ntoa(peer.sin_addr);
int port = ntohs(peer.sin_port);
std::cout << "get new link [" << ip << "] :" << port << std::endl;
pthread_t id;
Pragma*p=new Pragma(sockfd,ip,port);
pthread_create(&id,NULL,Routine,p);
}
}
static void *Routine(void *arg)
{
Pragma*p=(Pragma*)arg;
pthread_detach(pthread_self());
Server(p->sockfd,p->ip,p->port);
close(p->sockfd);
delete p;
}
static void Server(int sockfd, std::string ip, int port)
{
char buffer[1024];
while (true)
{
buffer[0] = '0';
int sz = read(sockfd, buffer, sizeof(buffer) - 1);
if (sz > 0)
{
buffer[sz] = '\0';
std::cout << ip << ":" << port << "#" << buffer << std::endl;
}
else if (sz == 0)
{
std::cerr << "client close" << std::endl;
break;
}
else
{
break;
}
}
close(sockfd);
}
~TcpServer()
{
if (listen_sock > 0)
close(listen_sock);
}
};