端口号(port)是传输层协议的内容.
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发给谁”;
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、 IPv6,以及后面要讲的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同
udp_server.hpp
#pragma once
#include "log.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SIZE 1024
class UdpServer
{
public:
// 绑定任意ip后, ip参数可以不写
UdpServer(uint16_t port, std::string ip = "0.0.0.0"):_port(port),_ip(ip),_sock(-1)
{}
// 这里是系统调用,来完成网络功能
bool initServer()
{
// 1. 创建套接字
// int socket(int domain, int type, int protocol);
// 参数1: 套接字类型- 网络通信或本地通信
// 参数2: 套接字的类别 - 面向数据报或面向数据流
//【与参数1区别:在确定以网络通信或本地通信后,是再以面向数据报还是面向数据流的方式通信呢】
// 参数3: 前两个参数确定后,就已经确定通信的协议了, 所以此参数忽略
_sock = socket(AF_INET, SOCK_DGRAM, 0);//网络通信以数据报的形式
if(_sock < 0)
{
logMessage(FATAL, "%d:%s", errno, strerror(errno));
exit(2);
}
// 2. bind:将用户设置的ip和port在内核中和我们当前的进程强关联
// int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
// 参数1: 将ip和port与套接字关联
// 参数2: struct sockaddr *(通用格式) 网络通信强转(struct sockaddr_in*) 本地通信强转(struct sockaddr_un*)
struct sockaddr_in local; // 里面需要填写三个参数 - 地址类型, 端口号, IP地址
bzero(&local, 0); // 初始化
local.sin_family = AF_INET; // (socket中是网络通信)结构体对象将会与网络服务器绑定
// 服务器的IP和端口未来也是要发送给对方主机的 -> 先要将数据发送到网络!网络是大端字节序
local.sin_port = htons(_port); // 主机序列转网络序列
//传入的ip是点分十进制字符串风格的IP地址 "192.168.110.132"
// sin_addr是一个结构体里面的s.addr是 uint32_t类型的 且ip为4字节的(所以上面ip需要转为4字节整形的网络序列)
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // inet_addr函数可以直接完成上面两步
//【服务器建议绑定任意ip】让服务器在工作过程中,可以从任意IP中获取数据
// 因为: 一台主机会存在多个ip,当绑定具体的ip后,服务器就只能收到发给这个具体ip的消息,如果绑定任意ip,那么
// 就是告诉操作系统,只要是发给这台主机的指定端口的任意消息都可以接受
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str()); // INADDR_ANY:宏就是0
if(bind(_sock,(struct sockaddr*)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "%d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "init udp server done ... %s", strerror(errno));
return true;
}
void start()
{
// 作为一款网络服务器,永远不退出的!
// 服务器启动-> 进程启动 -> 是一个常驻进程 -> 永远在内存中存在,除非挂了!
char buffer[SIZE];
for(;;)
{
// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
// 参数1:自己的套接字, 参数2:读取的信息放在自定义的缓冲区里面,参数3:缓冲区的长度,参数4:阻塞读取或非阻塞读取
// 参数5:接受到别人发给我的信息,我有可能还要回复信息,那么就需要知道对方的ip和port,所以src_addr里放的是别人的ip/port
// 参数5:是输出型参数, 需要自己定义一个这样的结构体, 让对方把信息填进去,这样自己就可以对方的ip和port
struct sockaddr_in peer; // peer,纯输出型参数
bzero(&peer, sizeof(peer));
// 【输入时len代表peer缓冲区大小,给别人大小】
// 【输出时len代表peer被对方填充后的大小】
socklen_t len = sizeof(peer); // 输入输出性参数
// start. 读取数据 flag: 阻塞方式
ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if(s > 0)
{
// 【发生成功进来】
buffer[s] = 0; // 读入结尾没有\0,需要自己加将数据当作一个字符串
// 将对方的ip和port填入peer
uint16_t cli_port = ntohs(peer.sin_port); // 从网络中来的!-- 需要网络转主机
std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的网络序列的IP -转- 本主机的字符串风格的IP,方便显示
printf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);
}
/*recvfrom收到了数据这里可以做处理数据*/
// 从peer知道对方的ip和port,可以给方发消息 sendto
// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
// 参数1: 自己的套接字,参数2: 存放发的消息,参数3:消息大小,参数3:是否阻塞,参数5: 发给谁,前面recvfrom里的peer就有了
sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len); // 这里是直接将收到的原封不动的发回去
}
}
~UdpServer()
{
if(_sock >= 0)
close(_sock);
}
private:
// 一个服务器必须要有ip地址和port(16位的整数)
std::string _ip;
uint16_t _port;
int _sock;
};
udp_server.cc
#include "udp_server.hpp"
#include
static void usage(std::string proc)
{
// std::cout<<"\nUsage: " << proc << " ip port\n" <
std::cout<<"\nUsage: " << proc << " port\n" <<std::endl;
}
// // ./udp_server ip port
// int main(int argc, char* argv[])
// {
// if(argc != 3)
// {
// usage(argv[0]); // 出错将使用手册打印出来
// exit(1);
// }
// std::string ip = argv[1];
// uint16_t port = atoi(argv[2]);
// std::unique_ptr svr(new UdpServer(port, ip));
// svr->initServer();
// svr->start();
// return 0;
// }
//【绑定任意ip,服务端server只需要port】
// ./udp_server port
int main(int argc, char* argv[])
{
if(argc != 2)
{
usage(argv[0]); // 出错将使用手册打印出来
exit(1);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<UdpServer> svr(new UdpServer(port));
svr->initServer();
svr->start();
return 0;
}
udp_client.cc
#include
#include
#include
#include
#include
#include
#include
#include
#include
static void usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " serverIp serverPort\n"<< std::endl;
}
// 解释 ./udp_client 127.0.0.1 8080 中 127.0.0.1
// 127.0.0.1用于本地回环: client和server发送数据只在本地协议栈中进行数据流动,不会把数据发送到网络中, 用于本地网络服务器的测试
// 注意: 云服务器无法绑定具体ip,如./udp_client 127.128.112.123 8080 或 0.0.0.0等非127.0.0.1的ip
// 服务器也不建议绑定具体的ip,建议绑定任意ip 具体看udp_server写法
// ./udp_client 127.0.0.1 8080
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(1);
}
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket error" << std::endl;
exit(2);
}
// server结构用于存放对方的ip和port
std::string message;
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
char buffer[1024];
while(true)
{
std::cout << "请输入你的信息# ";
std::getline(std::cin, message);
if(message == "quit") break;
// 当client首次发送消息给服务器的时候,OS会自动给client bind他的IP和PORT
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof server);
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr*)&temp, &len);
if(s > 0)
{
buffer[s] = 0;
std::cout << "server echo# " << buffer << std::endl;
}
}
close(sock);
return 0;
}
基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;
字符串转in_addr的函数:
in_addr转字符串的函数:
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void*addrptr。
关于inet_ntoa:
inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是否需要调用者手动释放呢?
man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.
那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:
因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果.
如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?
在APUE中, 明确提出inet_ntoa不是线程安全的函数;
但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁;
在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;
多线程调用inet_ntoa代码示例:
#include
#include
#include
#include
#include
#include
void *Func1(void *p)
{
struct sockaddr_in *addr = (struct sockaddr_in *)p;
while (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)
{
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;
}
写法一
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include "log.hpp"
static void service(int sock, const std::string& clientip, const uint16_t& clientport)
{
//echo server
char buffer[1024];
while(true)
{
// read && write 可以直接被使用!
ssize_t s = read(sock, buffer, sizeof(buffer)-1); // 相当于udp recvfrom
if(s > 0)
{
buffer[s] = 0; //将发过来的数据当做字符串
std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
}
else if(s == 0) //对端关闭连接
{
logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
break;
}
else
{
logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
break;
}
write(sock, buffer, strlen(buffer)); // 先当于 udp sendto
}
close(sock); // 这里的sock是serversock,一个客户端对应一个,当不再与这个客户端通信,需要关闭
}
class TcpServer
{
private:
// listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多
// 的连接请求就忽略, 这里设置不会太大(一般是5),
const static int gbacklog = 30;
public:
TcpServer(uint16_t port, std::string ip = "0.0.0.0"):_port(port),_listensock(-1)
{}
bool initServer()
{
// 1. 创建套接字 -- fd
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if(_listensock < 0)
{
logMessage(FATAL, "creat socket error, %d, %s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock: %d", _listensock); // 3
// 2. bind -- 文件 + 网络
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
// 3. 因为TCP是面向连接的,当正式通信的时候,需要先建立连接
if (listen(_listensock, gbacklog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
}
void start()
{
while (true)
{
// 4. 获取连接(将连接到的客户端ip和port放在src中)
struct sockaddr_in src;
socklen_t len = sizeof(src);
// servicesock 和 _listensock都是套接字 但是作用不一样
// _listensock是去获取连接的(像一个招揽工) servicesock是去提供服务的(像一个服务员)
// 举例: _listensock不断去招揽客人, 招揽到一个客人到店里面来了,那么就需要一个servicesock服务员去服务客人,每招揽一个,就需要一个servicesock
// 系统角度: 这里的sock就是一个文件描述符,打印_listensock是3,每次获取到一个客户端来连接服务器,就会分配一个文件描述符给servicesock
int servicesock = accept(_listensock, (struct sockaddr *)&src, &len);
if (servicesock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
continue;
}
// 获取连接成功了
uint16_t client_port = ntohs(src.sin_port);
std::string client_ip = inet_ntoa(src.sin_addr);
logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",servicesock, client_ip.c_str(), client_port);
// 方式一: 单进程循环 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
service(servicesock, client_ip, client_port);
// tcp 与 udp 中 strat 不一样
// udp start里面只有一个循环, 不断recvfrom 然后 sendto (所有的客户端都可以给服务器发消息,且都能收到)
// tcp start里面有两个死循环(A与B,A客户端先发消息, B再发消息,服务器只能收到A发的消息,当A客户端退出,B以前发的
// 消息一次显示到服务器)故tcp服务器只能服务一个客户端,主要原因是service函数收发消息是一个死循环,只有此客户端退出
// server函数结束,重新回到开始再与另一个客户端建立通信,拿到对方的ip和port
// 【可能会想为什么不和udp一样,收发消息不使用死循环】
// 因为tcp是需要和客户端建立连接的,而udp不需要,udp通信只要拿到服务器ip+port就可以直接给udp服务器发消息
// udp是不需要管客户端的存活
// tcp会给每个客户端分配一个sock来服务与tcp建立连接的客户端,是需要知道客户端的死活的,所以需要一个死循环保持与已建立
// 连接的客户端保持通信,当客户端关闭连接,tcp服务端会知道,会做出一些处理动作,并会关闭给客户端分配的sock
// 【所以tcp需要建立多个线程,每一个线程服务一个客户端】
}
}
~TcpServer()
{}
private:
uint16_t _port;
std::string _ip;
int _listensock;
};
写法二(对一的补充)
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ThreadPool/log.hpp"
#include "ThreadPool/threadPool.hpp"
#include "ThreadPool/Task.hpp"
static void service(int sock, const std::string& clientip, const uint16_t& clientport, const std::string& name) // 【方式五】调service,多了一个_name参数
{
//echo server
char buffer[1024];
while(true)
{
// read && write 可以直接被使用!
ssize_t s = read(sock, buffer, sizeof(buffer)-1); // 相当于udp recvfrom
if(s > 0)
{
buffer[s] = 0; //将发过来的数据当做字符串
std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
}
else if(s == 0) //对端关闭连接
{
logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
break;
}
else
{
logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
break;
}
write(sock, buffer, strlen(buffer)); // 先当于 udp sendto
}
close(sock); // 这里的sock是serversock,一个客户端对应一个,当不再与这个客户端通信,需要关闭
}
// // ThreadData对应【方式4】
// class ThreadData
// {
// public:
// std::string _ip;
// uint16_t _port;
// int _serversock;
// };
class TcpServer
{
private:
const static int gbacklog = 20; // 后面再说
// threadRoutine函数对应【方式4】 【方式5】不需要
// static void *threadRoutine(void *args)
// {
// pthread_detach(pthread_self());
// ThreadData *td = static_cast(args);
// service(td->_serversock, td->_ip, td->_port);
// delete td;
// return nullptr;
// }
public:
TcpServer(uint16_t port, std::string ip = "0.0.0.0")
: _port(port),
_ip(ip),
_listensock(-1),
_threadpool_ptr(ThreadPool<Task>::getThreadPool())
{}
bool initServer()
{
// 1. 创建套接字 -- fd
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if(_listensock < 0)
{
logMessage(FATAL, "creat socket error, %d, %s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock: %d", _listensock); // 3
// 2. bind -- 文件 + 网络
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
// 3. 因为TCP是面向连接的,当正式通信的时候,需要先建立连接
if (listen(_listensock, gbacklog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
return true;
}
void start()
{
// signal(SIGCHLD, SIG_IGN); // 【方式二】
_threadpool_ptr->run(); // 【方式五】
while (true)
{
// 4. 获取连接(将连接到的客户端ip和port放在src中)
struct sockaddr_in src;
socklen_t len = sizeof(src);
// servicesock 和 _listensock都是套接字 但是作用不一样
// _listensock是去获取连接的(像一个招揽工) servicesock是去提供服务的(像一个服务员)
// 举例: _listensock不断去招揽客人, 招揽到一个客人到店里面来了,那么就需要一个servicesock服务员去服务客人,每招揽一个,就需要一个servicesock
// 系统角度: 这里的sock就是一个文件描述符,打印_listensock是3,每次获取到一个客户端来连接服务器,就会分配一个文件描述符给servicesock
int servicesock = accept(_listensock, (struct sockaddr *)&src, &len);
if (servicesock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
continue;
}
// 获取连接成功了
uint16_t client_port = ntohs(src.sin_port);
std::string client_ip = inet_ntoa(src.sin_addr);
logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",servicesock, client_ip.c_str(), client_port);
// // 【方式一】: 单进程循环 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
// service(servicesock, client_ip, client_port);
// // 【方式二】- 多进程版 --- 创建子进程
// // 让子进程给新的连接提供服务,子进程可以打开父进程曾经打开的文件fd
// pid_t id = fork();
// assert(id != -1);
// if(id == 0)
// {
// // 子进程, 子进程会继承父进程打开的文件与文件fd(servicesock与_listensock)
// // 子进程是来进行提供服务的,不需要监听socket,所以不需要_listensock,要关闭
// close(_listensock);
// service(servicesock, client_ip, client_port);
// exit(0); // 僵尸状态
// }
// close(servicesock); // 如果父进程关闭servicesock,不会影响子进程,且父进程用不到servicesock,所以要关闭
// // waitpid(id, nullptr, 0); // 不能阻塞,要不然有和单线程版的server一样了
// // 但是不阻塞,需要一个vector来存放所有子进程的pid,然后循环join,回收资源
// // 使用vector太麻烦了,【好办法:使用信号】-- 放在start函数开始
// // signal(SIGCHLD, SIG_IGN); // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
// // 【方式三】多进程版 --- 创建子进程 -- 不适用信号
// pid_t id = fork();
// assert(id != -1);
// if(id == 0)
// {
// // 子进程
// close(_listensock);
// if(fork() > 0/*子进程本身*/) exit(0); //子进程本身立即退出
// // 孙子进程, 孤儿进程,OS领养, OS在孤儿进程退出的时候,由OS自动回收孤儿进程!
// service(servicesock, client_ip, client_port);
// exit(0);
// }
// // 父进程
// waitpid(id, nullptr, 0); //0:虽然是阻塞等待,但是不会阻塞,因为子进程已近退出了
// close(servicesock);
// // 【方案四】多线程
// ThreadData *td = new ThreadData();
// td->_serversock = servicesock;
// td->_ip = client_ip;
// td->_port = client_port;
// pthread_t pid;
// pthread_create(&pid, nullptr, threadRoutine, td);
// // 在多线程这里不用进程关闭_listensock,但要关闭servicesock, servicesock在service里面关
// // close(servicesock);
// 【方案五】线程池版本的多线程
Task t(servicesock, client_ip, client_port, service);
_threadpool_ptr->pushTask(t);
}
}
~TcpServer()
{}
private:
uint16_t _port;
std::string _ip;
int _listensock;
// 【方案五】线程池版本的多线程 -- 需要添加一个成员
std::unique_ptr<ThreadPool<Task>> _threadpool_ptr;
};
#include
#include
#include
#include
#include
#include
#include
#include
#include "ThreadPool/log.hpp"
static void Usage(const std::string proc)
{
std::cout<<"\nUsage: " << proc << " tcpserver ip port" << std::endl;
}
// ./tcp_client ip port
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
std:: string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
// 1. 创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
std::cerr<<"socket error"<<std::endl;
exit(2);
}
// client 不需要显示的bind,但是一定是需要port
// 需要让os自动进行port选择
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(serverip.c_str());
server.sin_port = htons(serverport );
// 2. 连接
if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
{
std::cerr << "connect error" << std::endl;
exit(3);
}
std::cout << "connect success" << std::endl;
// 3. 收发消息
while(true)
{
std::string line;
std::cout << "请输入# ";
std::getline(std::cin, line);
if(line == "quit")
break;
// connect后可以直接使用send与recv
ssize_t s = send(sock, line.c_str(), line.size(), 0); // 0阻塞式通信
if(s > 0)
{
char buffer[1024];
ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
buffer[s] = 0;
std::cout << "server 回显# " << buffer << std::endl;
}
else if(s == 0)
{
break;
}
else
{
break;
}
}
return 0;
}
UDP/TCP相关完整代码
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class Sock
{
private:
// listen的第二个参数,意义:底层全连接队列的长度 = listen的第二个参数+1
const static int gbacklog = 10;
public:
Sock() {}
static int Socket()
{
int listensock = socket(AF_INET, SOCK_STREAM, 0);
if (listensock < 0)
{
exit(2);
}
// 先断开链接一方会进入TIME_WITE状态,port依旧被占着,bind再次绑定会失败,setsockopt可以让bind成功
int opt = 1;
setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
return listensock;
}
static void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
{
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
exit(3);
}
}
static void Listen(int sock)
{
if (listen(sock, gbacklog) < 0)
{
exit(4);
}
}
// 一般经验
// const std::string &: 输入型参数
// std::string *: 输出型参数
// std::string &: 输入输出型参数
static int Accept(int listensock, std::string *ip, uint16_t *port)
{
struct sockaddr_in src;
socklen_t len = sizeof(src);
int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
if (servicesock < 0)
{
return -1;
}
if(port) *port = ntohs(src.sin_port);
if(ip) *ip = inet_ntoa(src.sin_addr);
return servicesock;
}
static bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
else return false;
}
~Sock() {}
};