UDP套接字是无连接协议,必须使用sendto函数发生数据,必须使用recvfrom函数接收数据,发生时序指明目的地址。
#include
#include
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);
参数:
sockfd:文件描述符
buf:发生数据的首地址
len:发生数据的长度
flags:
0:默认方式发生数据
MSG_OOB:发送带外数据
MSG_DONTRIUTE:告诉IP协议,目的主机在本地网络,没必要查找路由器
MSG_DONTWAIT:设置未非阻塞操作
MSG_NOSIGNAL:表示发送动作不愿被SIGPIPE信号终止
dest_addr:目标主机的IP地址和端口信息
addrlen:sizeof(dest_addr)
返回值:
成功:传输的字节数
失败:-1,失败原因存于错误码
接收对方发送的数据
#include
#include
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);
参数:
sockfd:文件描述符
buf:传入类型参数,接收数据的首地址
len:可接收数据的最大长度
flags:
0:默认方式接收数据
MSG_OOB:接收带外数据
MSG_PEEK:查看数据标志,返回的数据并不在系统中删除,如果再次调用recv函数会返回相同的数据内容
MSG_DONTWAIT:设置为非阻塞操作
NSG_WAITALL:强迫接收len大小的数据才返回,除非有错误或者信号参数
src_addr:传入类型参数,存放发送方的IP地址和端口信息
addrlen:
返回值:
成功:实际接收的字节数
失败:返回-1,失败原因存放在error中
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
class UdpSocket {
public:
UdpSocket() : fd_(-1) {
}
bool Socket() {
fd_ = socket(AF_INET, SOCK_DGRAM, 0);
if (fd_ < 0) {
perror("socket");
return false;
}
return true;
}
bool Close() {
close(fd_);
return true;
}
bool Bind(const std::string& ip, uint16_t port) {
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return false;
}
return true;
}
//这里的ip,port都是自定义的转出类型参数
//当需要获取发送方的ip和port的时候,就可以传入参数获取发送方的ip和port信息。
bool Recvfrom(std::string* buf, std::string* ip = NULL, uint16_t* port = NULL) {
char tmp[1024 * 10] = {0};
sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t read_size = recvfrom(fd_, tmp,
sizeof(tmp) - 1, 0, (sockaddr*)&peer, &len);
if (read_size < 0) {
perror("recvfrom");
return false;
}
// 将读到的缓冲区内容放到输出参数中
buf->assign(tmp, read_size);
if (ip != NULL) {
*ip = inet_ntoa(peer.sin_addr);
}
if (port != NULL) {
*port = ntohs(peer.sin_port);
}
return true;
}
bool Sendto(const std::string& buf, const std::string& ip, uint16_t port) {
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
ssize_t write_size = sendto(fd_, buf.data(), buf.size(), 0, (sockaddr*)&addr,sizeof(addr));
if (write_size < 0) {
perror("sendto");
return false;
}
return true;
}
private:
int fd_;
};
服务器只能被动的响应,不能主动的发送。
#pragma once
#include "udp_socket.hpp"
// C 式写法
// typedef void (*Handler)(const std::string& req, std::string* resp);
// C++ 11 式写法, 能够兼容函数指针, 仿函数, 和 lambda
#include
typedef std::function<void (const std::string&, std::string* resp)> Handler;
class UdpServer {
public:
UdpServer()
{
assert(sock_.Socket());
}
~UdpServer() {
sock_.Close();
}
//启动服务器函数
//handler是服务器具体的功能实现
bool Start(const std::string& ip, uint16_t port, Handler handler) {
// 1. 创建 socket
// 2. 绑定端口号
bool ret = sock_.Bind(ip, port);
if (!ret) {
return false;
}
// 3. 进入事件循环
for (;;) {
// 4. 尝试读取请求
std::string req;
std::string remote_ip;
uint16_t remote_port = 0;
bool ret = sock_.Recvfrom(&req, &remote_ip, &remote_port);
if (!ret) {
continue;
}
std::string resp;
// 5. 根据请求计算响应
handler(req, &resp);
// 6. 返回响应给客户端
sock_.Sendto(resp, remote_ip, remote_port);
printf("[%s:%d] req: %s, resp: %s\n", remote_ip.c_str(), remote_port,req.c_str(), resp.c_str());
}
sock_.Close();
return true;
}
private:
UdpSocket sock_;
};
#include"Udp_server.hpp"
#include
#include
std::unordered_map<std::string,std::string>dict;
void translate(const std::string&req,std::string* resp)
{
auto it= dict.find(req);
if(it==dict.end())
{
return ;
}
*resp=it->second;
}
int main(int argc,char*argv[])
{
if(argc!=3)
{
printf("Useage:./dict_server [ip] [port]...\n");
return 1;
}
// 1. 数据库初始化
dict.insert(std::make_pair("hello", "你好"));
dict.insert(std::make_pair("world", "世界"));
dict.insert(std::make_pair("c++", "最好的编程语言"));
dict.insert(std::make_pair("cxk", "鸡哥"));
dict.insert(std::make_pair("ikun","爱鲲"));
dict.insert(std::make_pair("理智","荔枝"));
UdpServer dict_server;
dict_server.Start(argv[1],atoi(argv[2]),translate);
return 0;
}
客户端要实现接收服务器的信息和发送信息给服务器的功能
#pragma once
#include "udp_socket.hpp"
class UdpClient {
public:
UdpClient(const std::string& ip, uint16_t port) : ip_(ip), port_(port) {
assert(sock_.Socket());
}
~UdpClient() {
sock_.Close();
}
//buf传入类型参数,获取服务器发送的信息
bool RecvFrom(std::string* buf) {
//不需要获取发送端的信息,所以不要传入ip和port参数
return sock_.RecvFrom(buf);
}
bool SendTo(const std::string& buf) {
return sock_.SendTo(buf,ip_, port_);
}
private:
UdpSocket sock_;
// 服务器端的 IP 和 端口号
std::string ip_;
uint16_t port_;
};
#include"Udp_client.hpp"
#include
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("Useage:./dict_client [ip] [port]...\n");
return 1;
}
Udpclient dict_client(argv[1],atoi(argv[2]));
for(;;)
{
std::string word;
std::cout<<"请输入要查询的单词:"<<std::endl;
std::cin>>word;
if(!std::cin)
{
std::cout<<"good bye"<<std::endl;
break;
}
dict_client.SendTo(word);
std::string result;
dict_client.RecvFrom(&result);
std::cout<<word<<"的意思是:"<<result<<std::endl;
}
return 0;
}
#include
int inet_pton(int af, const char *src, void *dst);
将点分十进制串转换为32位网络大端的数据
参数:
af【版本】: AF_INET【IPV4】,AF_INET6【IPV6】
src:点分十进制的首地址
dst:传入类型参数,转换的结果
char buf[]="192.168.2.125";
unsigned int num=0;
inet_pton(AF_INET,buf,&num);
unsigned char* p=(unsigned char*)#
printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));
输出:192 168 2 125
int inet_aton(const char* strptr,struct in_addr*addrptr)
将strptr所指C字符串转换成一个32位的网络字节序二进制值,并同过addrptr指针来存储,成功返回1,失败返回0
int ip_addr;
struct in_addr inet_ip_addr;
ip_addr = inet_aton("192.168.2.125", &inet_ip_addr);
unsigned char* p=(unsigned char*)&inet_ip_addr.s_addr;
printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));
输出:192 168 2 125
in_addr_t inet_addr(const char *strptr);
作用:将一个点分10进制转换为一个ip地址【整形】
int ip_addr;
ip_addr = inet_addr("192.168.2.125"); //设置ip点分十进制地址的地址
if(ip_addr==INADDR_NONE) // 返回值错误判断
printf("ERROR");
unsigned char* p=(unsigned char*)&ip_addr;
printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));//打印转换后的网络字节序
输出: 192 168 2 125
char *inet_ntoa(struct in_addr in);
in:ip地址
struct in_addr network;
network.s_addr=2097326272; //为s_addr赋值--网络字节序
printf("IP : %s\n", inet_ntoa(network));
输出:IP : 192.168.2.125
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
32位网络大端数据转换为点分十进制串
参数:
af【版本】: AF_INET【IPV4】,AF_INET6【IPV6】
src:32位网络大端数据
dst:存储点分十进制串地址
size:存储点分十进制串的长度,最多16位
返回值:
存储点分十进制串数组首地址
int main()
{
char ip[16];
struct in_addr net_ip;
net_ip.s_addr=2097326272;
const char* ret;
ret=inet_ntop(AF_INET,(void*)&net_ip.s_addr,ip,16);
printf("ip:%s\n",ip);
return 0;
}
输出:ip:192.168.2.125
#include
#include
#include
int main()
{
struct sockaddr_in addr1;
struct sockaddr_in addr2;
addr1.sin_addr.s_addr=0;
addr2.sin_addr.s_addr=0xffffffff;
char* ptr1=inet_ntoa(addr1.sin_addr);
char* ptr2=inet_ntoa(addr2.sin_addr);
printf("ptr1:%s\n",ptr1);
printf("ptr2:%s\n",ptr2);
return 0;
}
输出:
ptr1:255.255.255.255
ptr2:255.255.255.255
如果将inet_ntoa的调用顺序交换
输出:
ptr1:0.0.0.0
ptr2:0.0.0.0
上面的结果说明后续调用的inet_ntoa会覆盖掉前一次上一次调用的结果。
#include
#include
#include
#include
#include
#include
void* Func1(void* p) {
struct sockaddr_in* addr = (struct sockaddr_in*)p;
while (1) {
sleep(1);
char* ptr = inet_ntoa(addr->sin_addr);
printf("addr1: %s\n", ptr);
}
return NULL;
}
void* Func2(void* p) {
struct sockaddr_in* addr = (struct sockaddr_in*)p;
while (1) {
sleep(1);
char* ptr = inet_ntoa(addr->sin_addr);
printf("addr2: %s\n", ptr);
}
return NULL;
}
int main() {
pthread_t tid1 = 0;
struct sockaddr_in addr1;
struct sockaddr_in addr2;
addr1.sin_addr.s_addr = 0;
addr2.sin_addr.s_addr = 0xffffffff;
pthread_create(&tid1, NULL, Func1, &addr1);
pthread_t tid2 = 0;
pthread_create(&tid2, NULL, Func2, &addr2);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
在centos环境下,内部实现了锁,所以不会出现异常。
socket()函数
创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
#include
#include
int socket(int domain, int type, int protocol);
参数说明:
domain:IP地址类型
常用:AF_INET,AF_INET6
type:套接字类型
SOCK_STREAM:它提供基于字节流的有序、可靠、双向连接
可以支持带外数据传输机制。【流式套接字------用于TCP通信】
SOCK_DGRAM:支持数据报(固定最大值的无连接、不可靠消息长度)。【报文套接字----UDP通信】
SOCK_SEQPACKET
protocol:默认0
返回值:
成功:返回文件描述符
失败:-1
domain参数
connect()函数
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数
sockfd:sockfd套接字【文件描述符】
addr:套接字结构体的地址
addrlen:结构体的长度
bind()函数
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
参数:
sockfd:套接字
address:ip套接字结构体地址
addrlen:结构体大小
返回值:
成功返回0
失败返回-1
listen()函数
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
参数:
socket:套接字
backlog:已完成连接队列和未完成连接队列数之和的最大值 128
accept()函数
// 接收请求 (TCP, 服务器)
t
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
参数:
socket:监听套接字
address:传入类型参数,获取客户端的IP和端口信息
address_len:结构体大小的地址
返回值:
新的已连接套接字的文件描述符
TCP服务的通信流程
#include
ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
参数:
sockfd:发送端的的套接字
buff:存放接收数据的缓冲区
nbytes:指明buff的长度
flags:一般为0
ssize_t send(int s, const void *buff, size_t len, int flags);
参数:
s:指明发送端套接字描述符
buff:存放发送数据的缓冲区
len:指明发送的数据字节数
flags:0
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
class TcpSocket {
public:
TcpSocket() : fd_(-1) { }
TcpSocket(int fd) : fd_(fd) { }
bool Socket()
{
fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (fd_ < 0) {
perror("socket");
return false;
}
printf("open fd = %d\n", fd_);
return true;
}
bool Close() const
{
close(fd_);
printf("close fd = %d\n", fd_);
return true;
}
bool Bind(const std::string& ip, uint16_t port) const
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return false;
}
return true;
}
bool Listen(int num) const
{
//fd_变为监听套接字
int ret = listen(fd_,num);
if (ret < 0) {
perror("listen");
return false;
}
return true;
}
bool Accept(TcpSocket* peer, std::string* ip = NULL, uint16_t* port = NULL) const
{
sockaddr_in peer_addr;
socklen_t len = sizeof(peer_addr);
int new_sock = accept(fd_, (sockaddr*)&peer_addr, &len);
if (new_sock < 0) {
perror("accept");
return false;
}
printf("accept fd = %d\n", new_sock);
peer->fd_ = new_sock;
if (ip != NULL) {
*ip = inet_ntoa(peer_addr.sin_addr);
}
if (port != NULL) {
*port = ntohs(peer_addr.sin_port);
}
return true;
}
bool Recv(std::string* buf) const
{
buf->clear();
char tmp[1024 * 10] = {0};
// [注意!] 这里的读并不算很严谨, 因为一次 recv 并不能保证把所有的数据都全部读完
// 参考 man 手册 MSG_WAITALL 节.
ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);
if (read_size < 0) {
perror("recv");
return false;
}
if (read_size == 0) {
return false;
}
buf->assign(tmp, read_size);
return true;
}
bool Send(const std::string& buf) const
{
ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);
if (write_size < 0) {
perror("send");
return false;
}
return true;
}
bool Connect(const std::string& ip, uint16_t port) const
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
int ret = connect(fd_, (sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("connect");
return false;
}
return true;
}
int GetFd() const {
return fd_;
}
private:
int fd_;
}
服务端只能被动的发送信息。
#pragma once
#include
#include"TcpSocket.hpp"
typedef std::function<void (const std::string& req,std::string* resp)> Handler;
class tcp_server
{
public:
tcp_server(const std::string&ip,uint16_t& port):ip_(ip),port_(port)
{
//创建好套接字
listen_sock_.Socket();
}
~tcp_server()
{
listen_sock_.Close();
}
//服务器启动函数
bool start(Handler handler)
{
//创建套接字
//绑定ip和端口号
listen_sock_.Bind(ip_,port_);
//监听
listen_sock_.Listen(12);
//提取连接
//创建套接字,ip,port获取发送端的信息
for(;;)
{
//创建套接字,ip和port获取接收端的信息
//创建通信套接字
TcpSocket rsocket;
std::string ip;
uint16_t port;
//提取连接
if(!listen_sock_.Accept(&rsocket,&ip,&port))
{
continue;
}
printf("client的ip地址:%s,端口号:%d\n",ip.c_str(),port);
//通信
for(;;)
{
std::string req;
//读取请求,失败就结束循环
bool ret=listen_sock_.Recv(&req);
if(!ret)
{
printf("[ client %s %d ] disconnet\n",ip.c_str(),port);
//关闭rsocket
rsocket.Close();
break;
}
//接收响应
std::string resp;
handler(req,&resp);
//返回响应
rsocket.Send(resp);
printf("[%s %d] rep %s resp% s\n",ip.c_str(),port,req.c_str(),resp.c_str());
}
}
return true;
}
private:
TcpSocket listen_sock_;
std::string ip_;
uint16_t port_;
};
#include
#include"tcp_server.hpp"
#include
#include
void translate(const std:: string&req,std::string*resp)
{
auto it=dict.find(req);
if(it==dict.end())
{
*resp="未找到";
return ;
}
*resp=it->second;
}
//用map存储数据
std::unordered_map<std::string,std::string>dict;
int main(int argc,char*argv[])
{
if(argc!=3)
{
printf("useage:./dict_tcp_server [ip] [port]\n");
return 1;
}
//创建服务器
dict.insert(std::make_pair("hello", "你好"));
dict.insert(std::make_pair("world", "世界"));
dict.insert(std::make_pair("c++", "最好的编程语言"));
dict.insert(std::make_pair("cxk", "鸡哥"));
dict.insert(std::make_pair("ikun","爱鲲"));
dict.insert(std::make_pair("理智","荔枝"));
dict.insert(std::make_pair("爱好","唱 跳 rap 篮球"));
dict.insert(std::make_pair("香精煎鱼","想进监狱"));
Tcpserver server(argv[1], atoi(argv[2]));
server.start(translate);
return 0;
}
#pragma once
#include "tcp_socket.hpp"
class TcpClient
{
public:
TcpClient(const std::string& ip, uint16_t port) :ip_(ip), port_(port)
{
sock_.Socket();
}
~TcpClient()
{
sock_.Close();
}
bool Connect()
{
return sock_.Connect(ip_, port_);
}
bool Recv(std::string* buf)
{
return sock_.Recv(buf);
}
bool Send(const std::string& buf)
{
return sock_.Send(buf);
}
private:
TcpSocket sock_;
std::string ip_;
uint16_t port_;
};
#include "tcp_client.hpp"
#include
#include
int main(int argc,char*argv[])
{
if(argc!=3)
{
printf("useage:./dict_tcp_client [ip] [port]");
return 1;
}
//创建客户端
TcpClient client(argv[1], atoi(argv[2]));
bool ret=client.Connect();
if(!ret)
{
return 1;
}
for(;;)
{
std::cout<<"请输入你想要查询的单词:"<<std::endl;
std::string word;
std::cin>>word;
if(!std::cin)
{
break;
}
client.Send(word);
std::string buf;
client.Recv(&buf);
std::cout<<word<<": "<<buf<<std::endl;
}
return 0;
}
再启动一个客户端, 尝试连接服务器, 发现第二个客户端, 不能正确的和服务器进行通信.
分析原因, 是因为我们accecpt了一个请求之后, 就在一直while循环尝试read, 没有继续调用到accecpt, 导致不能接受新的请求。
可以使用多线程服务器,可以实现多个客户端的同时连接。实现的时候,只需要修改服务端即可。
typedef std::function<void (const std::string& req,std::string* resp)> Handler;
// 多进程版本的 Tcp 服务器
class TcpProcessServer
{
public:
TcpProcessServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port)
{
//创建套接字
listen_sock_.Socket()
// 需要处理子进程
//由内核回收子进程
signal(SIGCHLD, SIG_IGN);
}
void ProcessConnect(const TcpSocket& new_sock, const std::string& ip, uint16_t port,Handler handler)
{
int ret = fork();
if (ret > 0) 、
{
// 父进程不需要做额外的操作, 直接返回即可.
// 这里回收子进程不能使用wait,因为wait是阻塞等待的
//如果使用 wait , 会导致父进程不能快速再次调用到 accept, 仍然没法处理多个请求
new_sock.Close();
return;
}
else if (ret == 0)
{
// 处理具体的连接过程. 每个连接一个子进程
for (;;)
{
std::string req;
bool ret = new_sock.Recv(&req);
if (!ret)
{
// 当前的请求处理完了, 可以退出子进程了. 注意, socket 的关闭在析构函数中就完成了
printf("[client %s:%d] disconnected!\n", ip.c_str(), port);
exit(0);
}
std::string resp;
handler(req, &resp);
new_sock.Send(resp);
printf("[client %s:%d] req: %s, resp: %s\n", ip.c_str(), port,req.c_str(), resp.c_str());
}
}
else
{
perror("fork");
}
}
bool Start(Handler handler)
{
// 1. 创建 socket;
// 2. 绑定端口号
listen_sock_.Bind(ip_, port_);
// 3. 进行监听
listen_sock_.Listen(5)
// 4. 进入事件循环
for (;;)
{
// 5. 进行 accept
//创建通信套接字
TcpSocket new_sock;
std::string ip;
uint16_t port = 0;
if (!listen_sock_.Accept(&new_sock, &ip, &port))
{
continue;
}
printf("[client %s:%d] connect!\n", ip.c_str(), port);
ProcessConnect(new_sock, ip, port, handler);
}
return true;
}
private:
TcpSocket listen_sock_;
std::string ip_;
uint64_t port_;
};
#pragma once
#include
#include
#include "tcp_socket.hpp"
typedef std::function<void (const std::string&, std::string*)> Handler;
//通信套接字接口
struct ThreadArg {
TcpSocket new_sock;
std::string ip;
uint16_t port;
Handler handler;
};
class TcpThreadServer
{
public:
static void* ThreadEntry(void* arg)
{
// reinterpret_cast:强制类型转换
ThreadArg* p = reinterpret_cast<ThreadArg*>(arg);
ProcessConnect(p);
// 一定要记得释放内存!!! 也要记得关闭文件描述符
p->new_sock.Close();
delete p;
return NULL;
}
TcpThreadServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port)
{
listen_sock_.Socket();
}
bool Start(Handler handler)
{
// 1. 创建 socket;
// 2. 绑定端口号
listen_sock_.Bind(ip_, port_)
// 3. 进行监听
listen_sock_.Listen(5)
// 4. 进入循环
for (;;)
{
// 5. 进行 accept
ThreadArg* arg = new ThreadArg();
arg->handler = handler;
//6.提取连接,并提取连接的ip port 信息
bool ret = listen_sock_.Accept(&arg->new_sock, &arg->ip, &arg->port);
if (!ret)
{
continue;
}
printf("[client %s:%d] connect\n", arg->ip.c_str(), arg->port);
// 6. 创建新的线程完成具体操作
pthread_t tid;
pthread_create(&tid, NULL, ThreadEntry, arg);
pthread_detach(tid);
}
return true;
}
// 处理单次连接. 这个函数也得是 static
static void ProcessConnect(ThreadArg* arg)
{
// 1. 循环进行读写
for (;;)
{
std::string req;
// 2. 读取请求
bool ret = arg->new_sock.Recv(&req);
if (!ret)
{
printf("[client %s:%d] disconnected!\n", arg->ip.c_str(), arg->port);
break;
}
std::string resp;
// 3. 根据请求计算响应
arg->handler(req, &resp);
// 4. 发送响应
arg->new_sock.Send(resp);
printf("[client %s:%d] req: %s, resp: %s\n", arg->ip.c_str(),arg->port, req.c_str(), resp.c_str());
}
}
private:
//监听套接字
TcpSocket listen_sock_;
std::string ip_;
uint16_t port_;
};
服务器初始化:
1.调用监听socket, 创建文件描述符;
2.调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失
败;
3.调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;
4.调用accecpt,并阻塞, 等待客户端连接过来,获取一个通信套接字;
建立连接的过程:
这个建立连接的过程, 通常称为:三次握手
数据传输的过程
建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方可以同时写数据;
服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;
这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答;
服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求; 客户端收到后从read()返回, 发送下一条请求,如此循环下去;
断开连接的过程:
如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)
客户端收到FIN, 再返回一个ACK给服务器; (第四次)
这个断开连接的过程, 通常称为:四次挥手