源IP地址和目的IP地址
在IP数据报头部,有两个IP地址,分别叫做源IP(从哪来)和目的IP地址(到哪去)
端口号
端口号(port)是传输协议的内容
- 端口号是一个2字节16位的整数
- 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
- IP地址+端口号能够标识网络上的某一台主机的某一个进程
- 一个端口号只能被一个进程占用
socket通信,本质是进程间通信,跨网络的进程间通信
任何的网络服务与网络客户端,如果要进行正常的数据通信,必须要有端口号,来唯一标识自身
-》 在同一个OS内,一个进程可以与一个端口号进行绑定,该端口号就在网络层面唯一标识一台主机上唯一一个进程
公网ip:唯一标识全网内唯一的一台主机
端口号port:标识一台主机上的唯一一个进程
-》ip+port:标识全网内唯一的一个进程(socket通信)
进程的pid与port
port本质是是网络级的概念
进程本质是系统的概念
一台机器上,可能存在大量的进程,但不是所有的进程都要对外进行网络数据请求。
源端口号和目的端口号
传输层协议(TCP/UDP)的数据段有两个端口号,分别叫做源端口号(数据是谁发的)和目的端口号(要发给谁)
TCP协议
传输层协议:
- 有连接
- 可靠传输
- 面向字节流
UDP协议
传输层协议:
- 无连接
- 不可靠传输
- 面向数据报
网络字节序
内存中的多字节数据相对于内存地址有大小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分,网络数据流同样有大小端之分。
那么该如何帝国一网络数据流的地址?
- 发送主机通常将发送缓冲区数据的数据按内存地址从低到高的顺序发出
- 接受主机把从网络上接到字节一次保存在接受缓存去中,也是按内存地址从高到低的顺序保存
- 网络数据流的地址应:先发出的数据是低地址,后发出的地址是高地址
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
- 无论此主机是大端机还是小端机,都会按照TCP/IP规定的网络字节序来发送/接受数据
- 如果当前是小端,需将数据转成大端
网络字节序和主机字节序转换的函数
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
- 如果主机是小端字节序,这些函数将参数做成相应的大小端转换然后返回
- 如果主机是大端字节序,函数不做转换,将参数原封不动的返回
socket编程接口
常见API
//创建socket文件描述符(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);
//绑定端口号(TCP/UDP,服务器)
int bind(int socket,const struct sockaddr *address
,socklen_t address_len);
//开始监听socket(TCP,服务器)
int listen(int sockfd, int backlog);
//接受请求(TCP,服务器)
int accept(int sockfd, struct sockaddr *addr
,socklen_t *addrlen);
//建立连接(TCP,客户端)
int connect(int sockfd, const struct sockaddr *addr
,socklen_t addrlen);
sockaddr
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,eg:IPV4,IPV6以及UNIX Domain Socket,然而,各种网络协议的地址格式并不相同
- ipv4和ipv6的地址格式定义在netinet/in.h中 ,ipv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位ip地址
- ipv4,ipv6地址类型分别定义为常数AF_INET,AF_INET6。这样只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockadd结构体,就可以根据地址类型字段确定结构体中的内容
- socket API可以都用struct sockaddr* 类型来表示,在使用的时候需要强制转化成sockaddr_in;这样的好处是程序的通用性,可以接受IPV4,IPV6以及UNIX Domain Socket各种类型的sockaddr结构体指针作为参数
sockaddr结构
struct sockaddr
{
_SOCKADDR_COMMON(sa_);
//Common data:address family and length
char sa_data[14];//address data
}
sockaddr_in 结构
struct sockaddr_in
{
_SOCKADDR_COMMON(sin_);
in_port_t sin_port;//port number
struct in_addr sin_addr;//Internet address
unsigned char sin_zero[sizeof(struct sockaddr) -
_SOCKADDR_COMMON_SIZE - sizeof(in_port_t) -
sizeof(struct in_addr)];
}
该结构里主要有三部分信息:地址类型,端口号,ip地址
in_addr结构
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
}
简单的UDP网络程序
地址转化函数
字符串转in_addr函数
#include <arpa/inet.h>
int inet_aton(const char* strptr,struct in_addr* addrptr);
in_addr_t inet_addr(const char* strptr);
int inet_pton(int family,const char* strptr,void *addrptr);
in_addr转字符串函数
char* inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family,const void* addrptr,char* strptr);
其中inet_pton和inet_ntop不仅可以转换ipv4的in_addr,还可以转换ipv6的in6_addr,因此函数接口是void* addrptr;
inet_ntoa
inet_ntoa函数返回了一个char*,是该函式在内部申请了一块内存来保存ip,是把返回结果放到静态存储区,不需要我们手动释放。
因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果
netstat
netstat -nltp/-nlup
- netstat :用来查看当前的网络状态
- n: 能显示成数字就显示成数字
- l:list
- tp:tcp
- up:udp
udp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define DEFAULT 8081
class UdpServer
{
private:
int port;
int sockfd;
std::string ip;
public:
UdpServer(std::string _ip,int _port = DEFAULT):port(_port),sockfd(-1),ip(_ip)
{}
bool InitUdpserver()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
std::cerr<<"socket error"<<std::endl;
return false;
}
std::cout<<"socket create success,sockfd:"<<sockfd<<std::endl;
struct sockaddr_in local;
memset(&local,'\0',sizeof(local));
local.sin_family = AF_INET;//协议家族
local.sin_port = htons(port);//port需要发送到网络中,需要设置为网络序列
local.sin_addr.s_addr =inet_addr(ip.c_str()) ;//把字符串ip转化成整数ip
if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) < 0){
//绑定套接字
std::cerr <<"bind error" <<std::endl;
return false;
}
std::cout<<"bind success"<<std::endl;
return true;
}
void Start()
{
#define SIZE 128
char buffer[SIZE];//定义缓冲区
for(;;){
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//读取数据
if(size > 0)//读取成功
{
buffer[size] = 0;
int _port = ntohs(peer.sin_port);//把网络序列转换成主机序列
std::string _ip = inet_ntoa(peer.sin_addr);
std::cout<<_ip<<":"<<_port<<"#"<<buffer<<std::endl;
}
else{
std::cerr<<"recvfrom error"<<std::endl;
}
}
}
~UdpServer(){}
};
udp_server.cc
#include "udp_server.hpp"
//udp_server port
int main(int argc,char *argv[])
{
if(argc !=2){
std::cerr<<"Usage: "<<argv[0]<<"port"<<std::endl;
return 1;
}
std::string ip = "127.0.0.1";//127.0.0.1=localhost:表示本地主机-》本地环回
int port = atoi(argv[1]);
UdpServer *srv = new UdpServer(ip,port);
srv->InitUdpserver();
srv->Start();
return 0;
}
udp_client.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
class UdpClient
{
private:
int sockfd;
std::string server_ip;
int server_port;
public:
UdpClient(std::string _ip,int _port)
:server_ip(_ip),server_port(_port)
{}
bool InitUdpclient()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);//创建套接字
if(sockfd < 0)
{
std::cerr<<"sockfd create error"<<std::endl;
return false;
}
//客户端不需要绑定吗?
//客户端不需要port吗?
return true;
}
void Start()
{
struct sockaddr_in peer;
memset(&peer,0,sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(server_port);
peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
std::string msg;
for(;;)
{
std::cout<<"please Enter# ";
std::cin >> msg;
sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));//发送数据
}
}
~UdpClient(){}
};
udp_client.cc
#include "udp_client.hpp"
// ./udp_client server_ip server_port
int main(int argc,char* argv[])
{
if(argc !=3)
{
std::cerr<<"Usage"<<argv[0]<<"server_ip server_port"<<std::endl;
return 1;
}
std::string ip = argv[1];
int port = atoi(argv[2]);
UdpClient *ucli = new UdpClient(ip,port);
ucli->InitUdpclient();
ucli->Start();
return 0;
}
服务器对外网访问
云服务器的ip是由对应的云厂商提供的,这个ip不能直接绑定,如果需要bind,需要让外网放访问-》bind 0(意味着服务器可以接受来自任何客户端的请求)
如果想直接绑定:虚拟机或自定义安装的Linux
//INADDR_ANY ->0
udp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define DEFAULT 8081
class UdpServer
{
private:
int port;
int sockfd;
//std::string ip;
public:
//UdpServer(std::string _ip,int _port = DEFAULT):port(_port),sockfd(-1),ip(_ip){}
UdpServer(int _port = DEFAULT):port(_port),sockfd(-1)
{}
bool InitUdpserver()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
std::cerr<<"socket error"<<std::endl;
return false;
}
std::cout<<"socket create success,sockfd:"<<sockfd<<std::endl;
struct sockaddr_in local;
memset(&local,'\0',sizeof(local));
local.sin_family = AF_INET;//协议家族
local.sin_port = htons(port);//port需要发送到网络中,需要设置为网络序列
//local.sin_addr.s_addr =inet_addr(ip.c_str()) ;//把字符串ip转化成整数ip
local.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) < 0){
//绑定套接字
std::cerr <<"bind error" <<std::endl;
return false;
}
std::cout<<"bind success"<<std::endl;
return true;
}
void Start()
{
#define SIZE 128
char buffer[SIZE];//定义缓冲区
for(;;){
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//读取数据
if(size > 0)//读取成功
{
buffer[size] = 0;
int _port = ntohs(peer.sin_port);//把网络序列转换成主机序列
std::string _ip = inet_ntoa(peer.sin_addr);
std::cout<<_ip<<":"<<_port<<"#"<<buffer<<std::endl;
// 服务器发送信息
std::string echo_msg = "sever get->";
echo_msg += buffer;
sendto(sockfd,echo_msg.c_str(),echo_msg.size(),0,(struct sockaddr*)&peer,len);
}
else{
std::cerr<<"recvfrom error"<<std::endl;
}
}
}
~UdpServer()
{
if(sockfd >= 0)
{
close(sockfd);
}
}
};
udp_server.cc
#include "udp_server.hpp"
//udp_server port
int main(int argc,char *argv[])
{
if(argc !=2){
std::cerr<<"Usage: "<<argv[0]<<"port"<<std::endl;
return 1;
}
//std::string ip = "127.0.0.1";//127.0.0.1=localhost:表示本地主机-》本地环回
int port = atoi(argv[1]);
UdpServer *srv = new UdpServer(port);
srv->InitUdpserver();
srv->Start();
return 0;
}
udp_client.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <unistd.h>
class UdpClient
{
private:
int sockfd;
std::string server_ip;
int server_port;
public:
UdpClient(std::string _ip,int _port)
:server_ip(_ip),server_port(_port)
{}
bool InitUdpclient()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);//创建套接字
if(sockfd < 0)
{
std::cerr<<"sockfd create error"<<std::endl;
return false;
}
//客户端不需要绑定吗?
//客户端不需要port吗?
return true;
}
void Start()
{
struct sockaddr_in peer;
memset(&peer,0,sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(server_port);
peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
std::string msg;
for(;;)
{
std::cout<<"please Enter# ";
std::cin >> msg;
sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));//发送数据
char buffer[128];
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
if(size > 0)
{
buffer[size] = 0;
std::cout<<buffer<<std::endl;
}
}
}
~UdpClient()
{
if(sockfd >= 0)
{
close(sockfd);
}
}
};
udp_client.cc
// ./udp_client server_ip server_port
int main(int argc,char* argv[])
{
if(argc !=3)
{
std::cerr<<"Usage"<<argv[0]<<"server_ip server_port"<<std::endl;
return 1;
}
std::string ip = argv[1];
int port = atoi(argv[2]);
UdpClient *ucli = new UdpClient(ip,port);
ucli->InitUdpclient();
ucli->Start();
return 0;
}
优化
udp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#define DEFAULT 8081
class UdpServer
{
private:
int port;
int sockfd;
//std::string ip;
public:
//UdpServer(std::string _ip,int _port = DEFAULT):port(_port),sockfd(-1),ip(_ip){}
UdpServer(int _port = DEFAULT):port(_port),sockfd(-1)
{}
bool InitUdpserver()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
std::cerr<<"socket error"<<std::endl;
return false;
}
std::cout<<"socket create success,sockfd:"<<sockfd<<std::endl;
struct sockaddr_in local;
memset(&local,'\0',sizeof(local));
local.sin_family = AF_INET;//协议家族
local.sin_port = htons(port);//port需要发送到网络中,需要设置为网络序列
//local.sin_addr.s_addr =inet_addr(ip.c_str()) ;//把字符串ip转化成整数ip
local.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) < 0){
//绑定套接字
std::cerr <<"bind error" <<std::endl;
return false;
}
std::cout<<"bind success"<<std::endl;
return true;
}
void Start()
{
#define SIZE 128
char buffer[SIZE];//定义缓冲区
//输出的结果往网络里打印
//dup2(sockfd,1);//
for(;;){
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//读取数据
if(size > 0)//读取成功
{
buffer[size] = 0;
int _port = ntohs(peer.sin_port);//把网络序列转换成主机序列
std::string _ip = inet_ntoa(peer.sin_addr);
std::cout<<_ip<<":"<<_port<<"#"<<buffer<<std::endl;
std::string cmd = buffer;
std::string reslut;
if(cmd == "ls")
{
int pipes[2];
pipe(pipes);//创建匿名管道
pid_t id = fork();
if(id == 0)
{
//child
close(pipes[0]);
dup2(pipes[1],1);
execl("/usr/bin/ls","ls","-a","-l","-i",nullptr);
exit(1);
}
close(pipes[1]);
char c;
while(1)
{
if(read(pipes[0],&c,1) > 0)
{
reslut.push_back(c);
}
else
{
break;
}
}
wait(nullptr);
}
std::string echo_msg ;
if(reslut.empty())
{
// 服务器发送信息
echo_msg += buffer;
echo_msg = "sever get->";
}
else
{
echo_msg = reslut;
}
sendto(sockfd,echo_msg.c_str(),echo_msg.size(),0,(struct sockaddr*)&peer,len);
}
else{
std::cerr<<"recvfrom error"<<std::endl;
}
}
}
~UdpServer()
{
if(sockfd >= 0)
{
close(sockfd);
}
}
};
简单的TCP网络程序
为何一定要断开链接?
答:维护链接是有成本的(空间和时间),因为OS需要管理链接(先描述再组织)
TCP socket API
socket()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- socket()打开一个网络通讯端口,如果成功就会像open()一样返回一个文件描述符
- 应用程序可以像读写文件一样用read/write在网络上收发数据
- 如果socket()调用出错返回-1
- IPV4的family函数指定为AF_INET
- TCP协议的type参数指定为SOCK_STREAM表示面向流的传输协议
- protocol参数的介绍一般指定为0
bind()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
- 服务器监听的网络地址和端口号通常是不固定的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;服务器需要调用bind绑定一个固定的网络地址和端口号
- bind()成功返回0,失败返回-1
- bind()的作用是将参数sockfd和myaddr绑定在一起,使网络通讯的文件描述符sockfd监听myaddr所描述的地址和端口号
- struct sockaddr* 是一个通用指针类型。myaddr参数实际上可以接受多种协议的sockaddr结构体,但是它们的长度各不相同,所以需要addelen指定结构体长度
我们程序中对myaddr参数的初始化如下:
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
- 1、将整个结构体清0
- 2、设置地址为AF_INET
- 网络地址位INADDR_ANY,这个宏表示本地的任意地址ip,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定到底用哪个IP地址
- 端口号为SERV_PORT
listen()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于链接等待状态,如果接收到更多的连接请求就忽略
- listen()成功返回0失败返回-1
accept
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 三次握手完成后,服务器调用accept()接受连接
- 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来
- addr是一个传出参数,accept()返回时传出客户的地址和端口号
- 如果给addr参数传NULL,表示不关心客户端的地址
- addrlen参数是一个传入传出参数,传入的是调用者提供的,缓冲区addr的长度以避免缓冲区溢出的问题,传出的是客户端地址结构体的实际长度
单进程版本的TCP服务器
tcp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#define BACKLOG 5
#define DFL_PORT 8081
class Tcpserver
{
//TCP是面向连接的,所以需要在正式发送数据之前,先要建立链接
private:
int port;
int listen_sock;
public:
Tcpserver(int _port = DFL_PORT):port(_port),listen_sock(-1)
{}
bool InitTcpserver()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
std::cerr<<"socket error"<<std::endl;
exit(2);
}
//绑定
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = 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)//backlog:链接队列
{
std::cerr<<"listen error"<<std::endl;
exit(4);
}
return true;
}
void Loop()
{
for(;;)
{
//获取链接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
//listen_sock:获取新链接
//sock:服务 新链接(读取数据,分析处理数据,写入数据)
if(sock < 0)
{
std::cerr<<"accept error,continue"<<std::endl;
continue;
}
std::string ip = inet_ntoa(peer.sin_addr);
int sport = ntohs(peer.sin_port);
Service(sock,ip,sport);
std::cout<<"get a new link ["<< ip << "]"<< sport <<std::endl;
}
}
void Service(int sock,std::string ip,int sport)
{
char buffer[64];
while(true)
{
ssize_t size = read(sock,buffer,sizeof(buffer) - 1);//返回值>0实际读取了多少字节;==0:说明对端关闭链接;<0:读取错误
if(size > 0)
{
//成功
// std::cout << "get a new link["<< atoi(ip) "]"<<std::endl;
buffer[size] = 0;
std::cout<< ip << ":" << sport <<"# "<< buffer <<std::endl;
write(sock,buffer,size);//tcp socket中,读写都是一个文件描述符:sock(fd)
//全双工通信的体现
}
else if(size == 0)
{
//对端把链接关闭
std::cout<< ip << ":" << sport <<"close! " <<std::endl;
break;
}
else
{
//出错
std::cout << sock << "read error" << std::endl;
break;
}
}
close(sock);
std::cout << "service done!"<< std::endl;
}
~Tcpserver()
{
if(listen_sock >= 0)
{
close(listen_sock);
}
}
};
tcp_server.cc
#include "tcp_server.hpp"
void Usage(std::string proc)
{
std::cout<< "Usage: "<< proc << "port" << std::endl;
}
// ./server port
int main(int argc,char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
Tcpserver ts(atoi(argv[1]));
ts.InitTcpserver();
ts.Loop();
return 0;
}
tcp_client.hpp
#pragma once
#include <cstring>
#include <iostream>
#include <unistd.h>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
class Tcpclient
{
private:
std::string svr_ip;
int svr_port;
int sock;
public:
Tcpclient(std::string _ip,int _port):svr_ip(_ip),svr_port(_port),sock(-1)
{}
void InitTcpclient()
{
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
std::cerr << "socket error" << std::endl;
exit(2);
}
}
void Start()
{
struct sockaddr_in peer;
memset(&peer,0,sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(svr_port);
peer.sin_addr.s_addr = inet_addr(svr_ip.c_str());
//连接
if(connect(sock,(struct sockaddr*)&peer,sizeof(peer)) == 0)
{
//成功
std::cout << "connect success" << std::endl;
Request(sock);//与服务器端进行交互
}
else
{
//连接失败
std::cout << "connect false" << std::endl;
}
}
void Request(int sock)
{
std::string msg;
char buffer[1024];
while(true)
{
std::cout << "please enter# ";
std::cin >> msg;
//写入数据
write(sock,msg.c_str(),msg.size());
size_t size = read(sock,buffer,sizeof(buffer) - 1);
if(size > 0)
{
buffer[size] = 0;
std::cout << "server echo# " << buffer << std::endl;
}
}
}
~Tcpclient()
{
if(sock >= 0)
{
close(sock);
}
}
};
tcp_client.cc
#include "tcp_client.hpp"
void Usage(std::string proc)
{
std::cout << "Usage: "<<proc <<"server_ip server_port"<<std::endl;
}
// ./client server_ip srever_port
int main(int argc,char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
Tcpclient tc(argv[1],atoi(argv[2]));
tc.InitTcpclient();
tc.Start();
return 0;
}
多进程版本的TCP服务器
tcp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#define BACKLOG 5
#define DFL_PORT 8081
class Tcpserver
{
//TCP是面向连接的,所以需要在正式发送数据之前,先要建立链接
private:
int port;
int listen_sock;
public:
Tcpserver(int _port = DFL_PORT):port(_port),listen_sock(-1)
{}
bool InitTcpserver()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
std::cerr<<"socket error"<<std::endl;
exit(2);
}
//绑定
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = 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)//backlog:链接队列
{
std::cerr<<"listen error"<<std::endl;
exit(4);
}
return true;
}
void Loop()
{
//signal(SIGCHLD,SIG_IGN);
struct sockaddr_in peer;
for(;;)
{
//获取链接
socklen_t len = sizeof(peer);
int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
//listen_sock:获取新链接
//sock:服务 新链接(读取数据,分析处理数据,写入数据)
if(sock < 0)
{
std::cerr<<"accept error,continue"<<std::endl;
continue;
}
int sport = ntohs(peer.sin_port);
std::string ip = inet_ntoa(peer.sin_addr);
std::cout<<"get a new link-> "<< sock <<"["<< ip << "]"<< sport <<std::endl;
pid_t id = fork();
if(id == 0)
{
//child
close(listen_sock);
if(fork() > 0)
{
exit(0);
}
Service(sock,ip,sport);
exit(0);
}
close(sock);
waitpid(id,nullptr,0);
}
}
void Service(int sock,std::string ip,int sport)
{
char buffer[64];
while(true)
{
ssize_t size = read(sock,buffer,sizeof(buffer) - 1);//返回值>0实际读取了多少字节;==0:说明对端关闭链接;<0:读取错误
if(size > 0)
{
//成功
// std::cout << "get a new link["<< atoi(ip) "]"<<std::endl;
buffer[size] = 0;
std::cout<< ip << ":" << sport <<"# "<< buffer <<std::endl;
write(sock,buffer,size);//tcp socket中,读写都是一个文件描述符:sock(fd)
//全双工通信的体现
}
else if(size == 0)
{
//对端把链接关闭
std::cout<< ip << ":" << sport <<"close! " <<std::endl;
break;
}
else
{
//出错
std::cout << sock << "read error" << std::endl;
break;
}
}
close(sock);
std::cout << "service done!"<< std::endl;
}
~Tcpserver()
{
if(listen_sock >= 0)
{
close(listen_sock);
}
}
};
多线程版本的TCP服务器
tcp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#define BACKLOG 5
#define DFL_PORT 8081
class prama
{
public:
int sock;
std::string ip;
int port;
prama(int _sock,std::string _ip,int _port)
:sock(_sock),ip(_ip),port(_port)
{}
~prama()
{}
};
class Tcpserver
{
//TCP是面向连接的,所以需要在正式发送数据之前,先要建立链接
private:
int port;
int listen_sock;
public:
Tcpserver(int _port = DFL_PORT):port(_port),listen_sock(-1)
{}
bool InitTcpserver()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
std::cerr<<"socket error"<<std::endl;
exit(2);
}
//绑定
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = 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)//backlog:链接队列
{
std::cerr<<"listen error"<<std::endl;
exit(4);
}
return true;
}
static void* HandlerRequesr(void *arg)
{
//int sock = *(int*)arg;
prama *p = (prama*)arg;
pthread_detach(pthread_self());
Service(p->sock,p->ip,p->port);
close(p->sock);
delete p;
return nullptr;
}
void Loop()
{
//signal(SIGCHLD,SIG_IGN);
struct sockaddr_in peer;
for(;;)
{
//获取链接
socklen_t len = sizeof(peer);
int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
//listen_sock:获取新链接
//sock:服务 新链接(读取数据,分析处理数据,写入数据)
if(sock < 0)
{
std::cerr<<"accept error,continue"<<std::endl;
continue;
}
pthread_t tid;
//int *p = new int(sock);
std::string ip = inet_ntoa(peer.sin_addr);
int port = ntohs(peer.sin_port);
prama *p = new prama(sock,ip,port);
pthread_create(&tid,nullptr,HandlerRequesr,p);
}
}
static void Service(int sock,std::string ip,int sport)
{
char buffer[64];
while(true)
{
ssize_t size = read(sock,buffer,sizeof(buffer) - 1);//返回值>0实际读取了多少字节;==0:说明对端关闭链接;<0:读取错误
if(size > 0)
{
//成功
// std::cout << "get a new link["<< atoi(ip) "]"<<std::endl;
buffer[size] = 0;
std::cout<< ip << ":" << sport <<"# "<< buffer <<std::endl;
write(sock,buffer,size);//tcp socket中,读写都是一个文件描述符:sock(fd)
//全双工通信的体现
}
else if(size == 0)
{
//对端把链接关闭
std::cout<< ip << ":" << sport <<"close! " <<std::endl;
break;
}
else
{
//出错
std::cout << sock << "read error" << std::endl;
break;
}
}
//close(sock);
std::cout << "service done!"<< std::endl;
}
~Tcpserver()
{
if(listen_sock >= 0)
{
close(listen_sock);
}
}
};
线程池版本的TCP服务器
tcp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include "Threadpool.hpp"
#include "task.hpp"
#define BACKLOG 5
#define DFL_PORT 8081
class prama
{
public:
int sock;
std::string ip;
int port;
prama(int _sock,std::string _ip,int _port)
:sock(_sock),ip(_ip),port(_port)
{}
~prama()
{}
};
class Tcpserver
{
//TCP是面向连接的,所以需要在正式发送数据之前,先要建立链接
private:
int port;
int listen_sock;
Threadpool<Task> *tp;
public:
Tcpserver(int _port = DFL_PORT):port(_port),listen_sock(-1),tp(nullptr)
{}
bool InitTcpserver()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
std::cerr<<"socket error"<<std::endl;
exit(2);
}
//绑定
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = 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)//backlog:链接队列
{
std::cerr<<"listen error"<<std::endl;
exit(4);
}
tp = new Threadpool<Task>();
}
void Loop()
{
//signal(SIGCHLD,SIG_IGN);
tp->InitThreadpool();
struct sockaddr_in peer;
for(;;)
{
//获取链接
socklen_t len = sizeof(peer);
int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
//listen_sock:获取新链接
//sock:服务 新链接(读取数据,分析处理数据,写入数据)
if(sock < 0)
{
std::cerr<<"accept error,continue"<<std::endl;
continue;
}
int port = ntohs(peer.sin_port);
std::string ip = inet_ntoa(peer.sin_addr);
Task t(sock,ip,port);
tp->Push(t);
}
~Tcpserver()
{
if(listen_sock >= 0)
{
close(listen_sock);
}
}
};
ThreadPool.hpp
#pragma once
#define NUM 5
#include <iostream>
#include <queue>
#include <pthread.h>
template <typename T>
class Threadpool{
private:
int thread_num;
std::queue<T> task_queue;
pthread_mutex_t lock;
pthread_cond_t cond;
public:
Threadpool(int _num = NUM):thread_num(_num)
{
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
}
void Lockqueue()
{
pthread_mutex_lock(&lock);
}
void Unlockqueue()
{
pthread_mutex_unlock(&lock);
}
bool IsqueueEmpty()
{
return task_queue.size() == 0 ? true : false;
}
void Wait()
{
pthread_cond_wait(&cond,&lock);
}
void Wakeup()
{
pthread_cond_signal(&cond);
}
static void *Routine(void *arg)
{
pthread_detach(pthread_self());//线程分离
Threadpool *self = (Threadpool*)arg;
while(true){
self->Lockqueue();
while(self->IsqueueEmpty()){
//wait
self->Wait();
}
//任务队列有任务
T t;
self->Pop(t);
self->Unlockqueue();
//处理任务
t.Run();
}
}
void Push(const T& in)
{
Lockqueue();
task_queue.push(in);
Unlockqueue();
Wakeup();
}
void Pop(T& out)
{
out = task_queue.front();
task_queue.pop();
}
void InitThreadpool()
{
pthread_t tid;
for(int i = 0;i < thread_num;i++){
pthread_create(&tid,nullptr,Routine,this);//无法访问static 修饰的函数,所以传 this
}
}
~Threadpool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
task.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
class Handler
{
public:
Handler()
{}
void operator()(int sock,std::string ip,int port)
{
char buffer[64];
while(true)
{
ssize_t size = read(sock,buffer,sizeof(buffer) - 1);//返回值>0实际读取了多少字节;==0:说明对端关闭链接;<0:读取错误
if(size > 0)
{
//成功
// std::cout << "get a new link["<< atoi(ip) "]"<<std::endl;
buffer[size] = 0;
std::cout<< ip << ":" << port <<"# "<< buffer <<std::endl;
write(sock,buffer,size);//tcp socket中,读写都是一个文件描述符:sock(fd)
//全双工通信的体现
}
else if(size == 0)
{
//对端把链接关闭
std::cout<< ip << ":" << port <<"close! " <<std::endl;
break;
}
else
{
//出错
std::cout << sock << "read error" << std::endl;
break;
}
}
//close(sock);
std::cout << "service done!"<< std::endl;
close(sock);
}
~Handler()
{}
};
class Task
{
private:
int sock;
std::string ip;
int port;
Handler handler;
public:
Task()
{}
Task(int _sock,std::string _ip,int _port)
:sock(_sock),ip(_ip),port(_port)
{}
void Run()
{
handler(sock,ip,port);
}
~Task()
{}
};
实现简单的英汉互译
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <unordered_map>
class Handler
{
public:
Handler()
{}
void operator()(int sock,std::string ip,int port)
{
std::unordered_map<std::string,std::string> dict;
dict.insert({"apple","苹果"});
dict.insert({"banana","香蕉"});
dict.insert({"insert","插入"});
dict.insert({"left","左边"});
dict.insert({"right","右边"});
char buffer[64];
std::string value;
while(true)
{
ssize_t size = read(sock,buffer,sizeof(buffer) - 1);//返回值>0实际读取了多少字节;==0:说明对端关闭链接;<0:读取错误
if(size > 0)
{
//成功
// std::cout << "get a new link["<< atoi(ip) "]"<<std::endl;
buffer[size] = 0;
std::cout<< ip << ":" << port <<"# "<< buffer <<std::endl;
std::string key = buffer;
auto it = dict.find(key);
if(it != dict.end())
{
value = it->second;
}
else
{
//没找到
value = buffer;
}
write(sock,value.c_str(),value.size());//tcp socket中,读写都是一个文件描述符:sock(fd)
//全双工通信的体现
}
else if(size == 0)
{
//对端把链接关闭
std::cout<< ip << ":" << port <<"close! " <<std::endl;
break;
}
else
{
//出错
std::cout << sock << "read error" << std::endl;
break;
}
}
//close(sock);
std::cout << "service done!"<< std::endl;
close(sock);
}
~Handler()
{}
};
class Task
{
private:
int sock;
std::string ip;
int port;
Handler handler;
public:
Task()
{}
Task(int _sock,std::string _ip,int _port)
:sock(_sock),ip(_ip),port(_port)
{}
void Run()
{
handler(sock,ip,port);
}
~Task()
{}
};