目录
发展背景:
最早时期计算机是单机工作,由于两台主机之间无法进行信息交互,只能通过拷贝实现。因此出现了 交换机,实现两台相邻设备之间的数据交换,但是交换机没有动态选择路径的能力,无法指定设备进行交换信息。后来交换机被 路由器取代,随着交换的目标不同路由器实现自主路径选择,实现了小型局域网。
网络分类:
局域网(覆盖范围1km以内),城域网(1km~2km),广域网(2km以上):根据网络覆盖范围进行的网络划分。
互联网,因特网:相同的东西不同的叫法-国际性的更大的广域网
以太网,令牌环网:组网技术
一、数据传输
IP地址
IP地址:网络中每一台主机的唯一标识(不能出现重复)
ipv4:数据类型uint32_t 类型的整数:无符号四字节整数。
由于ipv4地址不够用,有了 DHCP(动态地址分配:谁用给谁分配)----NAT(网络地址转换:组建私网,私网的主机使用同一ip地址)
ipv6:uint8_t ip[16]:有16个字节,每个字节有8位,共有128位;不向前兼容ipv4
端口
每一个网络中的数据都会带有信息:源端IP+源端端口+对端IP+对端端口
由来:当主机A发送给主机B消息时,无法判断主机B上哪个软件接收消息,因此需要端口进行判断。
端口:网络通信中一台主机上进程的标识符
unit16_t类型的数据(无符号2字节整数)–范围:0~65535
每一个网络中的数据不但带有源端IP和对端IP,还包含两个信息:源端端口,对端端口
目的:为了让接收方主机收到数据后能够直到应该哪个进程处理数据
一个端口只能被一个进程占用
源端IP+源端端口+对端IP+对端端口:描述了当前这个数据是从网络中对端哪个主机上的哪个进程发出来的,要发往哪台主机上的哪个进程。
协议
协议:约定–网络通信协议实际上就是网络通信两端的数据格式约定
协议由来:数据传输过程中以物理信号传递,最后要通过网卡转换为数据信号,但不同的网卡转换方式不同,造成信息转换错误。
网络通信协议–就是网络通信中的数据格式
因此要实现网络互联就必须具有统一的网络通信协议标准
协议分层:将复杂网络环境按照提供的服务以及使用的接口协议不同划分层次将网络通信层次清晰划分出来,更加容易实现网络互连。
OSI七层网络互连模型:以提供的服务不同将网络通信环境划分为七层
层次划分:应用层-表示层-会话层-传输层-网络层-链路层-物理层
TCP/IP五层模型:
应用层:负责进程(应用程序)之间的数据沟通;FTP、HTTP
传输层:负责进程之间的数据传输;UDP、TCP
网络层:负责地址管理与路由选择;IP;路由器(进行路径选择)
链路层:负责相邻设备之间的数据传输;以太网协议-eth;交换机
物理层:负责物理光电信号的传输;以太网协议;集线器;
网络字节序
通信两端主机字节序不同有可能造成数据二义
字节序:cpu对内存中数据进行存取的顺序
主机字节序分类:
大端:低地址存高位;小端:低地址存低位
主机字节序对网络通信的影响:
两个不同主机字节序主机之间的数据通信,主机字节序不同,则有可能在网络通信中会产生数据二义性。
字节序针对的数据类型:
存储单元大于一个字节的数据类型:short/int/long/float/double;
char字符串不需要考虑字节序,单字节存储
解决方案:
定义网络通信字节序标准
网络中的数据必须都是网络字节序-大端字节序
发送放将数据转换为网络字节序(大端)的数据进行发送
接收方接收到数据之后根据自己的主机字节序决定如何转换
概括:想要避免因为主机字节序不同而导致的数据二义,则需要在网络中统一字节序标准–网络字节序–为大端字节序(也就意味着如果你的主机是小端,则在网络通信时需要将数据转换为网络字节序(大端)后进行发送)
大小端区分:
一个变量/数组的起始地址就是低地址,随着下标变大而变大
X86-小端;MIPS-大端
使用联合体判断大小端:
union{int a; char b;} temp; tmp.a = 1; if(tmp.b == 1)则是小端;
联合体成员变量共用一块地址空间,以空间大的成员变量为空间大小,int四个字节,char一个字节,b在首地址(低地址),a设为1,则低位是1,如果b也等于1,说明低位存放在低地址,为小端;如果b为0,则说明低位存放在高地址,为大端。
网络通信–五元组
网络每一条通信都会包含有完整的五元组信息:数据从哪来到哪去,数据格式什么样子
五元组-表示一条通信:sip+sport+dip+dport+protlcol
源端ip+源端端口+对端ip+对端端口+通信协议
二、socket套接字
socket套接字编程:网络通信程序的编写(接口+流程)
1、udp、tcp区别
udp协议与tcp协议的区别初识:
udp协议:用户数据报协议
特性:无连接,不可靠,面向数据报
应用场景:实时性大于安全性要求的场景—视频传输
tcp协议:传输控制协议
特性:面向连接,可靠传输,面向字节流
应用场景:安全性大于实时性要求的场景—文件传输
在网络通信程序中,通信两端被分为:客户端,服务器端
客户端:通常是提供给客户的通信端,通常是编写通信程序中主动发起请求的一段
服务端:通常指被动接受请求,提供服务的通信端
客户端时主动发送请求的一端,也就意味着客户端必须提前能够知道服务端的地址信息(ip+port)是多少;服务端的地址信息通常是固定的,并且是提前提供给客户端的。
2、套接字接口(udp通信程序)
通信流程:
服务端:server
1.创建套接字—在内核中创建一个socket结构体
2.为套接字绑定地址信息
在创建套接字创建的socket结构体中加入IP+port信息
①告诉操作系统主机收到的哪些数据应该交给当前的这个socket
②确定发送数据的源端地址信息
3.接收数据
当前进程从指定的socket接收缓冲区中取出数据
4.发送数据
将要发送的数据放到socket发送缓冲区中,内核选择合适时候封装发送
5.关闭套接字
客户端:client
1.创建套接字
2.为套接字绑定地址信息
大多是情况下会忽略第2步
在发送数据时,若socket没有绑定地址,则系统会选择合适的地址进行绑定
3.发送数据
4.接收数据
5.关闭套接字
接口代码(udp)
1.创建套接字:
int socket(int domain, int type, int protocol);
domain:地址域类型;AF_INET:使用ipv4通信,使用ipv4的地址结构
type:套接字类型;SOCK_STREAM / SOCK_DGRAM
udp通信(面向数据包)必须使用SOCK_DGRAM;tcp通信(面向字节流)必须使用SOCK_STREAM
protocol:本次通信协议 IPPROTO_TCP-6 / IPPROTO_UDP-17
返回值:返回一个文件描述符-操作句柄,失败返回-1
通过操作句柄能在内核中找到相对应的socket结构体
2.为套接字绑定地址信息
int bing(int sockfd, struct sockaddr *addr, socklen_t addrlen);
sockfd:创建套接字返回的操作句柄
addr:当前绑定的地址信息
socklen_t:地址信息长度
返回值:成功返回0;失败返回-1
3.接收数据信息
ssize_t recvfrom(int sockfd, void *buf, int len, int flag, struct sockaddr *srcaddr, socklen_t *addrlen);
sockfd:操作句柄;
buf:空间地址,用于存放接收的数据
len:要接受的数据长度
flag:选项标志-默认0-表示阻塞接收
srcaddr:本条数据的源端地址信息
addrlen:输入输出参数–指定要接受多长的地址结构,并且返回实际接收的地址长度
返回值:返回实际接收到的数据长度;失败错误返回-1
4.发送数据:
ssize_t sendto(int sockfd, void *data, int len, int flag, struct sockaddr *peeraddr, socklen_t addrlen);
sockfd:操作句柄;
data:要发送的数据的空间首地址
len:要发送的数据长度
flag:默认0-阻塞发送
peeraddr:对端地址信息
addrlen:地址结构长度
返回值:成功返回实际发送的数据长度,失败返回-1;
5.关闭套接字
int close(int fd);
3、套接字接口(tcp通信程序)
通信流程:
服务端:server
1.创建套接字—在内核中创建一个socket结构体
2.为套接字绑定地址信息
在创建套接字创建的socket结构体中加入IP+port信息
①告诉操作系统主机收到的哪些数据应该交给当前的这个socket
②确定发送数据的源端地址信息
3.开始监听
告诉系统可以开始处理客户端的连接请求了
系统会为每一个新客户端创建一个新的套接字
4.获取新建连接
5.收发数据(使用的是新建的套接字)
此时新的套接字有源端对端的地址信息,无所谓谁先发数据
6.关闭套接字
客户端:client
1.创建套接字
2.为套接字绑定地址信息
大多是情况下会忽略第2步(不推荐主动绑定)
在发送数据时,若socket没有绑定地址,则系统会选择合适的地址进行绑定
3.像服务端发起连接请求
4.收发数据
5.关闭套接字
接口代码(tcp)
前两步一样
1.创建套接字:
int socket(int domain, int type, int protocol);
//
domain:地址域类型;AF_INET:使用ipv4通信,使用ipv4的地址结构
type:套接字类型;SOCK_STREAM / SOCK_DGRAM
udp通信(面向数据包)必须使用SOCK_DGRAM;tcp通信(面向字节流)必须使用SOCK_STREAM
protocol:本次通信协议 IPPROTO_TCP-6 / IPPROTO_UDP-17
返回值:返回一个文件描述符-操作句柄,失败返回-1
通过操作句柄能在内核中找到相对应的socket结构体
2.为套接字绑定地址信息
int bing(int sockfd, struct sockaddr *addr, socklen_t addrlen);
sockfd:创建套接字返回的操作句柄
addr:当前绑定的地址信息
socklen_t:地址信息长度
返回值:成功返回0;失败返回-1
3.服务端:开始监听:
int listen(int sockfd, int backlog)
sockfd:描述符
backlog:服务端能够在同一事件处理的最大连接数
3.客户端:发送连接请求
int connect(int sockfd, struct sockaddr *srvaddr, socklen_t len);
sockfd:描述符;
srvaddr:服务端地址信息;
len:地址长度;
返回值:成功返回0;失败返回-1;
4.服务端获取新建连接
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
sockfd:监听套接字(服务端最早创建的套接字–只用于获取新建连接)
cliaddr:新的连接的客户端地址信息
addrlen:输入输出参数,指定地址信息长度,以及返回实际长度
返回值:新建连接的套接字描述符–往后与客户端的通信都通过这个描述符完成
5.收发数据:tcp通信因为socket结构中包含完整五元组因此不需要指定地址
ssize_t send(int sockfd, void *data, int len, int flag);
sockfd:描述符;
data:要发送的数据;
len:数据长度;
flag:0
返回值:成功返回实际发送的数据长度;失败返回-1;连接断开会触发异常
ssize_t recv(int sockfd, void *buf, int len, int flag);
sockfd:描述符;
buf:空间地址;
len:要接收的数据长度;
flag:0
返回值:成功返回实际接受的数据长度;出错返回-1;连接断开返回0
6.关闭套接字
int close(int fd);
4、字节序转换接口
uint32_t htonl(uint32_t hostlong);//32位数据主机到网络字节序转换
uint16_t htons(uint16_t hostshort);//16位数据主机到网络字节序转换
uint32_t ntonl(uint32_t netlong);//32位数据网络到主机字节序转换
uint32_t ntons(uint32_t netshort);//16位数据网络到主机字节序转换
port端口转换使用s,ip转换用l,不能混用
//将字符串点分十进制IP地址转换为整型网络字节序IP地址
”192.168.2.2“-> 0xc0a80202
in_addr_t inet_addr(const char *cp);
//将网络字节序IP地址转换为字符串点分十进制IP地址
char *inet_ntoa(struct in_addr in); in.s_addr = 0xc0a80202
const char *inet_ntop(int af, void *src, char *dst, socklen_t size);
int inet_pton(int af, const char *src, void *dst);
三、代码实例
1、udp通信程序的编写
服务器端
使用C语言实现服务器端代码(udp协议)
//udp_srv.c
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h> //字节序转换接口头文件
#include<netinet/in.h> //地址结构,协议类型头文件
#include<sys/socket.h> //套接字接口头文件
int main()
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror(socket error);
return -1;
}
//2.为套接字绑定地址信息
struct sockaddr_in addr;//定义ipv4地址结构
addr.sin_family = AF_INET;//ipv4的地址结构,告诉剩下的该如何解析,2个字节的端口,4个字节的ip地址
addr.sin_port = htons(9000);//设置地址端口
addr.sin_addr.s_addr = inet_addr("10.106.200.199");//ifconig,云服务器选eth0,虚拟机选ens3
int len = sizeof(addr);
int ret = bind(sockfd, (struct sockaddr*)&addr, len);
if(ret < 0)
{
perror("bind error");
return -1;
}
while(1)
{
//3.接收数据
char buf[1024] = {0};
struct sockaddr_in paddr;
int len = sizeof(struct sockaddr_in);
ret = recvfrom(sockfd, buf, 1023, 0, (struct sockaddr*)&paddr, &len);
if(ret < 0)
{
perror("recvfrom error");
return -1;
}
uint16_t client_port = ntohs(paddr.sin_port);
char *client_ip = inet_ntoa(paddr.sin_addr);
printf("client-[%s:%d] say: %s\n", client_ip, client_port, buf);
//4.回复数据
memset(buf, 0x00, 1024);
printf("server say:");
fflush(stdout);
fgets(buf, 1023, stdin);
//char *data = "receive success";
ret = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&paddr, len);
if(ret < 0)
{
perror("sendto error");
return -1;
}
}
//5.关闭套接字
close(sockfd);
return 0;
}
客户端
使用C++语言封装udp类
//udpsocket.hpp
/*
* 封装实现一个udpsocket类
* 通过实例化的对象调用对应的成员接口,可以实现udp客户端或服务端的搭建
*/
#include<cstdio>
#include<iostream>
#include<string>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
class UdpSocket
{
private:
int _sockfd;
public:
UdpSocket()
: _sockfd(-1)
{}
bool Socket()
{
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(_sockfd < 0)
{
perror("socket error");
return false;
}
return true;
}
bool Bind(std::string &ip, uint16_t port)
{
//ipv4地址结构
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret;
ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if(ret < 0)
{
perror("bing error");
return false;
}
return true;
}
bool Send(std::string &data, const std::string &ip, const int port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = sendto(_sockfd, data.c_str(), data.size(), 0, (sockaddr*)&addr, len);
if(ret < 0)
{
perror("sendto error");
return false;
}
return true;
}
//作为客户端,在发送数据的时候已经知道服务端的ip和port,因此在接收时如果ip和port默认为空则不需要获取地址信息
bool Recv(std::string *buf, std::string *ip = NULL, int *port = NULL)
{
struct sockaddr_in addr;
socklen_t len = sizeof(struct sockaddr_in);
char tmp[4096] = {0};
int ret = recvfrom(_sockfd, tmp, 4096, 0, (sockaddr*)&addr, &len);
if(ret < 0)
{
perror("recvfrom error");
return false;
}
//assign接口:将temp、ret传进去,首先给buf开辟ret长度空间,将temp数据拷贝到buf内
//自带申请空间拷贝数据
buf->assign(tmp, ret);
if(ip != NULL)
{
*ip = inet_ntoa(addr.sin_addr);
}
if(port != NULL)
{
*port = ntohs(addr.sin_port);
}
return true;
}
bool Close()
{
if(_sockfd != -1)
{
close(_sockfd);
}
return true;
}
};
//udp_cli.cpp
#include "udpsocket.hpp"
#define CHECK_RET(q) if((q)==false){return -1;}
int main()
{
UdpSocket sock;
//1.创建套接字
CHECK_RET(sock.Socket());
//2.绑定地址信息(不推荐)
WHILE(1)
{
//3.发送数据
std::cout << "client say:";
std::string buf;
std::cin >> buf;
CHECK_RET(sock.Send(buf, "10.106.200.199", 9000));
//4.接收数据
buf.clear();
CHECK_RET(sock.Recv(&buf));
std::cout << "server say:" << buf << "\n";
}
//5.关闭套接字
sock.Close();
return 0;
}
2、tcp通信程序的编写
1.tcp套接字头文件编写
#include<cstdio>
#include<iostream>
#include<string>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#define CHECK_RET(q) if((q)==false){return -1;}
#define LISTEN_BACKLOG 5
class TcpSocket
{
private:
int _sockfd;
public:
TcpSocket()
: _sockfd(-1)
{}
bool Socket()
{
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(_sockfd < 0)
{
perror("socket error");
return false;
}
return true;
}
bool Bind(const std::string &ip, const uint16_t port)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(&ip[0]);
socklen_t len = sizeof(sockaddr);
int ret;
ret = bind(_sockfd, (sockaddr*)&addr, len);
if(ret < 0)
{
perror("bind error");
return false;
}
return true;
}
bool Listen(int backlog = LISTEN_BACKLOG)
{
int ret = listen(_sockfd, backlog);
if(ret < 0)
{
perror("listen error");
return false;
}
return true;
}
bool Connect(const std::string &ip, const int port)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(&ip[0]);
socklen_t len = sizeof(sockaddr);
int ret;
ret = connect(_sockfd, (sockaddr*)&addr, len);
if(ret < 0)
{
perror("connect error");
return false;
}
return true;
}
bool Accept(TcpSocket *sock, std::string *ip = NULL, uint16_t *port = NULL)
{
//int accept(监听套接字, 客户端地址, 长度)
sockaddr_in addr;
socklen_t len = sizeof(sockaddr_in);
int newfd = accept(_sockfd, (sockaddr*)&addr, &len);
if(newfd < 0)
{
perror("accept error");
return false;
}
sock->_sockfd = newfd;
if(ip != NULL)
{
*ip = inet_ntoa(addr.sin_addr);
}
if(port != NULL)
{
*port = ntohs(addr.sin_port);
}
return true;
}
bool Recv(std::string *buf)
{
//int recv(描述符,空间,数据长度,标志位)
//返回值:实际获取大小,0-连接断开;-1-出错
char tmp[4096] = {0};
int ret = recv(_sockfd, tmp, 4096, 0);
if(ret < 0)
{
perror("recv error");
return false;
}
else if(ret == 0)
{
printf("peer shutdown");
return false;
}
buf->assign(tmp, ret);
return true;
}
bool Send(const std::string &data)
{
//int send(描述符,数据,长度,标志位)
int total = 0;
while(total < data.size())
{
int ret = send(_sockfd, &data[0]+total, data.size()-total, 0);
if(ret < 0)
{
perror("send error");
return false;
}
total += ret;
}
return true;
}
bool Close()
{
if(_sockfd != -1)
{
close(_sockfd);
}
return true;
}
};
2.tcp服务端编写
#include "tcpsocket.hpp"
int main(int argc, char *argv[])
{
//通过程序运行参数执行服务端要绑定的地址
//./tcp_srv 192.168.2.2 9000
if(argc != 3)
{
printf("usage: ./tcp_src 10.106.200.199 9000\n");
return -1;
}
std::string srvip = argv[1];
uint16_t srvport = std::stoi(argv[2]);
TcpSocket lst_sock;//监听套接字
//1.创建套接字
CHECK_RET(lst_sock.Socket());
//2.绑定地址信息
CHECK_RET(lst_sock.Bind(srvip, srvport));
//3.开始监听
CHECK_RET(lst_sock.Listen());
while(1)
{
//4.获取新建连接
TcpSocket clisock;
std::string cliip;
uint16_t cliport;
bool ret = lst_sock.Accept(&clisock, &cliip, &cliport);
if(ret == false)
{
continue;
}
std::cout << "get new conn:" << cliip << "-" << cliport << "\n";
//5.收发数据 --使用获取的新建套接字进行通信
std::string buf;
ret = clisock.Recv(&buf);
if(ret == false)
{
clisock.Close();
continue;
}
std::cout << "client say:" << buf << std::endl;
buf.clear();
std::cout << "server say:";
std::cin >> buf;
ret = clisock.Send(buf);
if(ret == false)
{
clisock.Close();
}
}
//6.关闭套接字
lst_sock.Close();
return 0;
}
3.tcp客户端编写
#include "tcpsocket.hpp"
int main(int argc, char *argv[])
{
//通过参数传入要连接的服务端的地址信息
if(argc != 3)
{
printf("usage: ./tcp_lic srvip srvport\n");
return -1;
}
std::string srvip = argv[1];
uint16_t srvport = std::stoi(argv[2]);
TcpSocket cli_sock;
//1.创建套接字
CHECK_RET(cli_sock.Socket());
//2.绑定地址信息(不推荐)
//3.向服务端发起连接
CHECK_RET(cli_sock.Connect(srvip, srvport));
while(1)
{
//4.收发数据
std::string buf;
std::cout << "client say:";
std::cin >> buf;
CHECK_RET(cli_sock.Send(buf));
buf.clear();
CHECK_RET(cli_sock.Recv(&buf));
std::cout << "server say:" << buf << std::endl;
}
//5.关闭套接字
CHECK_RET(cli_sock.Close());
return 0;
}
问题:
服务端只能与一个客户端通信一次
tcp_srv中
accept与recv以及send都是阻塞接口,任意一个接口的调用,都有可能会导致服务端流程阻塞
本质原因:当前的服务端,在没有新连接到来时accept阻塞,没数据时recv阻塞。因为不知道什么时候有新连接到来,什么时候哪个客户端有数据到来,因此流程只能固定的去调用接口,但是这种调用当时有可能会造成阻塞
解决方案:多执行流并发处理
在服务端accept获取新连接之后创建一个新的执行流与客户端进行通信。
好处;
1.主线程卡在获取新建连接这里,但是不影响客户端的通信
2.某个客户端通信阻塞,也不会影响主线程以及其他线程
在主线程中,获取新建连接,一旦获取到了则创建一个执行流,通过这个新建连接与客户端进行通信。
多线程:普通线程与主线程数据共享,指定入口函数执行
主线程不能随意释放套接字,因为资源共享,一旦释放其他进程无法使用
多进程:子进程复制了父进程,但是数据独有
1.注意僵尸进程的处理
2.注意父子进程数据各自独有,父进程用不到套接字资源因此创建子进程之后直接释放掉,否则会造成资源泄露
tcp服务端改进(多线程)
#include "tcpsocket.hpp"
#include<pthread.h>
void *thr_entry(void *arg)
{
bool ret;
TcpSocket *clisock = (TcpSocket*)arg;
while(1)
{
//5.收发数据 --使用获取的新建套接字进行通信
std::string buf;
ret = clisock->Recv(&buf);
if(ret == false)
{
clisock->Close();
delete clisock;
return NULL;
}
std::cout << "client say:" << buf << std::endl;
buf.clear();
std::cout << "server say:";
std::cin >> buf;
ret = clisock->Send(buf);
if(ret == false)
{
clisock->Close();
delete clisock;
return NULL;
}
}
delete clisock;
return NULL;
}
int main(int argc, char *argv[])
{
//通过程序运行参数执行服务端要绑定的地址
//./tcp_srv 192.168.2.2 9000
if(argc != 3)
{
printf("usage: ./tcp_src 10.106.200.199 9000\n");
return -1;
}
std::string srvip = argv[1];
uint16_t srvport = std::stoi(argv[2]);
TcpSocket lst_sock;//监听套接字
//1.创建套接字
CHECK_RET(lst_sock.Socket());
//2.绑定地址信息
CHECK_RET(lst_sock.Bind(srvip, srvport));
//3.开始监听
CHECK_RET(lst_sock.Listen());
while(1)
{
//4.获取新建连接
TcpSocket *clisock = new TcpSocket();
std::string cliip;
uint16_t cliport;
bool ret = lst_sock.Accept(clisock, &cliip, &cliport);
if(ret == false)
{
continue;
}
std::cout << "get new conn:" << cliip << "-" << cliport << "\n";
//创建线程专门负责与指定客户端的通信
pthread_t tid;
pthread_create(&tid, NULL, thr_entry, (void*)clisock);
pthread_detach(tid);
}
//6.关闭套接字
lst_sock.Close();
return 0;
}
tcp服务端改进(多进程)
#include "tcpsocket.hpp"
#include<signal.h>
#include<sys/wait.h>
void worker(TcpSocket &clisock)
{
//child process
bool ret;
while(1)
{
//5.收发数据 --使用获取的新建套接字进行通信
std::string buf;
ret = clisock.Recv(&buf);
if(ret == false)
{
clisock.Close();
exit(0);
}
std::cout << "client say:" << buf << std::endl;
buf.clear();
std::cout << "server say:";
std::cin >> buf;
ret = clisock.Send(buf);
if(ret == false)
{
clisock.Close();//释放的是子进程的clisock
exit(0);
}
}
clisock.Close();
exit(0);
}
void sigcb(int no)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, char *argv[])
{
//通过程序运行参数执行服务端要绑定的地址
//./tcp_srv 192.168.2.2 9000
if(argc != 3)
{
printf("usage: ./tcp_src 10.106.200.199 9000\n");
return -1;
}
//避免子进程退出后成为僵尸进程的两种方式
//1
signal(SIGCHLD, SIG_IGN);
//2
signal(SIGCHLD, sigcb);
std::string srvip = argv[1];
uint16_t srvport = std::stoi(argv[2]);
TcpSocket lst_sock;//监听套接字
//1.创建套接字
CHECK_RET(lst_sock.Socket());
//2.绑定地址信息
CHECK_RET(lst_sock.Bind(srvip, srvport));
//3.开始监听
CHECK_RET(lst_sock.Listen());
while(1)
{
//4.获取新建连接
TcpSocket clisock;
std::string cliip;
uint16_t cliport;
bool ret = lst_sock.Accept(&clisock, &cliip, &cliport);
if(ret == false)
{
continue;
}
std::cout << "get new conn:" << cliip << "-" << cliport << "\n";
pid_t pid = fork();
if(pid < 0)
{
clisock.Close();
continue;
}
else if(pid == 0)
{
//child process
worker(clisock);
}
//父子进程数据独有,父进程关闭不会对子进程造成影响
clisock.Close();//释放的是父进程中的clisock
}
//6.关闭套接字
lst_sock.Close();
return 0;
}
四、查看网络状态
//查看当前套接字的信息/网络监听状态
netstat -anptu
-a:查看网络所有信息
-t:查看tcp信息
-u:查看udp信息
-n:地址和端口不以服务名称显示,以原始地址端口显示
-p:查看当前网络状态信息对应的进程