本篇文章默认你对UDP协议和TCP协议已经理解掌握了:UDP协议详解、TCP协议详解
UDP网络通信编程
首先我们需要知道 UDP协议是传输层协议,面向数据报,无连接,不可靠传输,实时性高。
创建套接字:使进程与网卡之间建立联系
domain:地址域–>使用AF_INET(IPV4)
type:套接字类型
SOCK_STREAM–流式套接字–默认对应的协议:TCP
SOCK_DGRAM–数据报套接字–默认对应的协议:UDP
protocol:协议类型
套接字类型默认协议–0
IPPROTO_TCP–6
IPPROTO_UDP–17
返回值:文件描述符–套接字操作句柄
为套接字绑定地址信息(ip/port)
sockfd:套接字操作句柄,socket函数的返回值
addr:地址类型(IPV4或者IPV6等等)
addrlen:地址信息长度
返回值:成功返回0,失败返回-1
客户端首先向服务端发送数据
服务端通常必须固定一个地址信息,不能随意改变;但是客户端的地址可以随意,因为数据发送给服务端,服务端就能获知客户端的地址。
sockfd:套接字操作句柄,socket函数的返回值
buf:要发送什么数据
len:数据的长度
flags:通常置0,阻塞操作(即缓冲区放满后等待)
dest_addr:接收端地址信息(ip+port)
addrlen:接收端地址信息长度
返回值:返回实际发送的数据长度,失败:-1
服务端接收数据
客户端发送的数据到达服务端主机后,服务端操作系统根据这个数据的地址信息,决定将这个数据放到哪一个套接字的缓冲区中(数据由指定进程处理);服务端通过创建套接字返回的描述符,在内核中找到套接字结构体,进而从缓冲区中取出数据。
sockfd:套接字操作句柄,socket函数的返回值
buf:从接收缓冲区拿到的数据存在在哪里
len:接收buf定义的最大长度,预留\0的位置
flags:通常置0,阻塞操作(即缓冲区中没有数据时等待)
src_addr:发送端地址信息(ip+port)
addrlen:发送端地址信息长度
返回值:返回实际接收的数据长度,失败:-1
关闭套接字–>释放内核中套接字占据的资源
fd:套接字操作句柄,socket函数的返回值
模拟实现UDP网络通信
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>
#define PORT 17777
class UdpSvr
{
public:
UdpSvr()
{
sock_ = -1;
}
~UdpSvr()
{}
bool Create()
{
sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sock_ < 0)
{
std::cout << "Create failed" << std::endl;
return false;
}
return true;
}
bool Bind()
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(sock_, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
std::cout << "Bind failed" << std::endl;
return false;
}
return true;
}
bool Send(std::string& msg, struct sockaddr_in* peeraddr)
{
ssize_t send_size = sendto(sock_, msg.c_str(), msg.size(), 0, (struct sockaddr*)peeraddr, sizeof(struct sockaddr_in));
if(send_size < 0)
{
std::cout << "Send failed" << std::endl;
return false;
}
return true;
}
bool Recv(std::string& msg, struct sockaddr_in* peeraddr)
{
char buf[1024] = {0};
socklen_t peeraddr_len = sizeof(struct sockaddr_in);
ssize_t recv_size = recvfrom(sock_, &buf, sizeof(buf) - 1, 0, (struct sockaddr*)peeraddr, &peeraddr_len);
if(recv_size < 0)
{
std::cout << "Recv failed" << std::endl;
return false;
}
msg.assign(buf, recv_size);
return true;
}
void Close()
{
close(sock_);
sock_ = -1;
}
private:
int sock_;
};
#include "udpsvr.hpp"
int main()
{
UdpSvr us;
if(!us.Create())
{
return 0;
}
if(!us.Bind())
{
return 0;
}
while(1)
{
std::string msg;
struct sockaddr_in cliaddr;
if(!us.Recv(msg, &cliaddr))
{
continue;
}
std::cout << "client say:" << msg << std::endl;
std::cout << "server say:";
fflush(stdout);
std::cin >> msg;
if(!us.Send(msg, &cliaddr))
{
continue;
}
}
us.Close();
return 0;
}
#include "udpsvr.hpp"
int main(int argc, char* argv[])
{
if(argc != 2)
{
std::cout << "./cli [ip]" << std::endl;
return 0;
}
std::string ip = argv[1];
UdpSvr us;
if(!us.Create())
{
return 0;
}
struct sockaddr_in svraddr;
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(PORT);
svraddr.sin_addr.s_addr = inet_addr(ip.c_str());
while(1)
{
std::string msg;
std::cout << "client say:";
fflush(stdout);
std::cin >> msg;
if(!us.Send(msg, &svraddr))
{
continue;
}
if(!us.Recv(msg, &svraddr))
{
continue;
}
std::cout << "server say:" << msg << std::endl;
}
us.Close();
return 0;
}
TCP网络通信编程
首先我们需要知道 TCP协议是传输层协议,面向字节流,面向连接,可靠传输,安全性高。
创建套接字:使进程与网卡之间建立联系
domain:地址域–>使用AF_INET(IPV4)
type:套接字类型
SOCK_STREAM–流式套接字–默认对应的协议:TCP
SOCK_DGRAM–数据报套接字–默认对应的协议:UDP
protocol:协议类型
套接字类型默认协议–0
IPPROTO_TCP–6
IPPROTO_UDP–17
返回值:文件描述符–套接字操作句柄
为套接字绑定地址信息(ip/port)
sockfd:套接字操作句柄,socket函数的返回值
addr:地址类型(IPV4或者IPV6等等)
addrlen:地址信息长度
返回值:成功返回0,失败返回-1
客户端流程
1、创建套接字:使进程与网卡之间建立联系
2、为套接字绑定地址信息(ip/port)
3、向服务端发起连接请求
sockfd:套接字描述符
addr:服务端的地址信息(当前客户端需要连接哪一个服务端的地址信息)
addrlen:地址信息长度
4、发送数据
sockfd:accept函数返回的操作系统内核新创建的socket操作句柄
buf:要给对端发送什么数据
len:发送数据的长度
flags:默认置 0–>阻塞发送
5、接收数据
sockfd:accept函数返回的操作系统内核新创建的socket操作句柄
buf:从接收缓冲区接收的数据放到哪里去
len:最大能放多少
flags:
0:阻塞发送
MSG_PEEK:探测接收,即不会将接收缓冲区当中的数据移除,而是拷贝接收缓冲区的数据(接收缓冲区当中原有的数据依然存在)
6、关闭套接字–>释放内核中套接字占据的资源
fd:套接字操作句柄,socket函数的返回值
服务端流程
1、创建套接字:使进程与网卡之间建立联系
2、为套接字绑定地址信息(ip/port)
3、监听
sockfd:监听套接字的操作句柄
backlog:已完成连接队列的大小
同一时刻,服务端最大的并发连接数
监听的时候,一旦有新的连接到来,OS会对新的连接分配一个socket,进行一对一服务。
4、获取连接
sockfd:监听套接字的操作句柄
addr:客户端地址信息
addrlen:客户端地址信息的长度,输入输出型参数
返回值:返回的是操作系统内核创建的新的socket的文件描述符
5、接收数据
6、发送数据
7、关闭套接字–>释放内核中套接字占据的资源
模拟实现TCP网络通信
Tcp通信时必须使用多进程或者多线程:因为accept会阻塞
对于单个执行流而言,由于代码是串行运行的
- 将accept函数放到while(1)循环当中
每次循环要往下继续执行,则必须有一个新的连接到来,并且在往下执行的时候,是和新的客户端沟通 - 将accept函数放到while(1)循环外面
则整个服务器只能和一个客户端进行业务的收发数据,但是并不能代表不能和服务器建立连接,以为TCP的三次握手是在listen阶段,告诉操作系统可以监听,连接的建立右操作系统完成,完成后的连接放到已完成连接队列当中去。
多进程
#pragma once
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#define PORT 17778
class TcpSvr
{
public:
TcpSvr()
{
sock_ = -1;
}
~TcpSvr()
{}
bool CreateSock()
{
sock_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sock_ < 0)
{
std::cout << "Create failed" << std::endl;
return false;
}
return true;
}
bool Bind()
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(sock_, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
std::cout << "Bind failed" << std::endl;
return false;
}
return true;
}
bool Listen(int backlog = 5)
{
int ret = listen(sock_, backlog);
if(ret < 0)
{
std::cout << "Listen failed" << std::endl;
return false;
}
return true;
}
bool Accept(TcpSvr& ts, struct sockaddr_in* addr = NULL)
{
struct sockaddr_in cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
int newsock = accept(sock_, (struct sockaddr*)&cliaddr, &cliaddr_len);
if(newsock < 0)
{
std::cout << "Accept failed" << std::endl;
return false;
}
ts.sock_ = newsock;
if(addr != NULL)
{
memcpy(addr, &cliaddr, cliaddr_len);
}
return true;
}
bool Connect(struct sockaddr_in* svraddr)
{
int ret = connect(sock_, (struct sockaddr*)svraddr, sizeof(struct sockaddr_in));
if(ret < 0)
{
std::cout << "Connect failed" << std::endl;
return false;
}
return true;
}
bool Send(std::string& msg)
{
ssize_t send_size = send(sock_, msg.c_str(), msg.size(), 0);
if(send_size < 0)
{
std::cout << "Send failed" << std::endl;
return false;
}
return true;
}
bool Recv(std::string& msg)
{
char buf[1024] = {0};
ssize_t recv_size = recv(sock_, buf, sizeof(buf) - 1, 0);
if(recv_size < 0)
{
std::cout << "Recv failed" << std::endl;
return false;
}
else if(recv_size == 0)
{
std::cout << "Peer close connect" << std::endl;
return false;
}
msg.assign(buf, recv_size);
return true;
}
void Close()
{
close(sock_);
sock_ = -1;
}
private:
int sock_;
};
#include "tcpsvr.hpp"
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
void sigcb(int signo)
{
//等待子进程
(void)signo;
while(1)
{
waitpid(-1, NULL, WNOHANG);
}
}
int main()
{
signal(SIGCHLD, sigcb);
TcpSvr ts;
if(!ts.CreateSock())
{
return 0;
}
if(!ts.Bind())
{
return 0;
}
if(!ts.Listen())
{
return 0;
}
while(1)
{
TcpSvr ts2;
struct sockaddr_in cliaddr;
if(!ts.Accept(ts2, &cliaddr))
{
continue;
}
std::cout << "Server have a new connect, " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;
int pid = fork();
if(pid < 0)
{
std::cout << "Create fork failed" << std::endl;
exit(1);
}
else if(pid == 0)
{
//child
while(1)
{
std::string msg;
if(!ts2.Recv(msg))
{
break;
}
std::cout << "cilent say:" << msg << std::endl;
std::cout << "server say:";
fflush(stdout);
std::cin >> msg;
ts2.Send(msg);
}
ts2.Close();
exit(1);
}
else
{
//father
ts2.Close();
}
}
ts.Close();
return 0;
}
#include "tcpsvr.hpp"
int main(int argc, char* argv[])
{
if(argc != 2)
{
std::cout << "./cli [ip]" << std::endl;
return 0;
}
std::string ip = argv[1];
TcpSvr ts;
if(!ts.CreateSock())
{
return 0;
}
struct sockaddr_in svraddr;
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(PORT);
svraddr.sin_addr.s_addr = inet_addr(ip.c_str());
if(!ts.Connect(&svraddr))
{
return 0;
}
while(1)
{
std::string msg;
std::cout << "client say:";
fflush(stdout);
std::cin >> msg;
ts.Send(msg);
ts.Recv(msg);
std::cout << "server say:" << msg << std::endl;
}
ts.Close();
return 0;
}
多线程
#pragma once
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#define PORT 17778
class TcpSvr
{
public:
TcpSvr()
{
sock_ = -1;
}
~TcpSvr()
{}
bool CreateSock()
{
sock_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sock_ < 0)
{
std::cout << "Create failed" << std::endl;
return false;
}
return true;
}
bool Bind()
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(sock_, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
std::cout << "Bind failed" << std::endl;
return false;
}
return true;
}
bool Listen(int backlog = 5)
{
int ret = listen(sock_, backlog);
if(ret < 0)
{
std::cout << "Listen failed" << std::endl;
return false;
}
return true;
}
bool Accept(TcpSvr* ts, struct sockaddr_in* addr = NULL)
{
struct sockaddr_in cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
int newsock = accept(sock_, (struct sockaddr*)&cliaddr, &cliaddr_len);
if(newsock < 0)
{
std::cout << "Accept failed" << std::endl;
return false;
}
ts->sock_ = newsock;
if(addr != NULL)
{
memcpy(addr, &cliaddr, cliaddr_len);
}
return true;
}
bool Connect(struct sockaddr_in* svraddr)
{
int ret = connect(sock_, (struct sockaddr*)svraddr, sizeof(struct sockaddr_in));
if(ret < 0)
{
std::cout << "Connect failed" << std::endl;
return false;
}
return true;
}
bool Send(std::string& msg)
{
ssize_t send_size = send(sock_, msg.c_str(), msg.size(), 0);
if(send_size < 0)
{
std::cout << "Send failed" << std::endl;
return false;
}
return true;
}
bool Recv(std::string& msg)
{
char buf[1024] = {0};
ssize_t recv_size = recv(sock_, buf, sizeof(buf) - 1, 0);
if(recv_size < 0)
{
std::cout << "Recv failed" << std::endl;
return false;
}
else if(recv_size == 0)
{
std::cout << "Peer close connect" << std::endl;
return false;
}
msg.assign(buf, recv_size);
return true;
}
void Close()
{
close(sock_);
sock_ = -1;
}
private:
int sock_;
};
#include "tcpsvr.hpp"
void* ThreadStart(void* arg)
{
pthread_detach(pthread_self());
TcpSvr* ts = (TcpSvr*)arg;
while(1)
{
std::string msg;
ts->Recv(msg);
std::cout << "client say:" << msg << std::endl;
std::cout << "server say:";
fflush(stdout);
std::cin >> msg;
ts->Send(msg);
}
ts->Close();
delete ts;
}
int main()
{
TcpSvr ts;
if(!ts.CreateSock())
{
return 0;
}
if(!ts.Bind())
{
return 0;
}
if(!ts.Listen())
{
return 0;
}
while(1)
{
TcpSvr* ts2 = new TcpSvr();
struct sockaddr_in cliaddr;
if(!ts.Accept(ts2, &cliaddr))
{
continue;
}
std::cout << "Server have a new conncet, " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;
pthread_t tid;
int ret = pthread_create(&tid, NULL, ThreadStart, (void*)ts2);
if(ret < 0)
{
std::cout << "Create thread failed" << std::endl;
return 0;
}
}
ts.Close();
return 0;
}
#include "tcpsvr.hpp"
int main(int argc, char* argv[])
{
if(argc != 2)
{
std::cout << "./cli [ip]" << std::endl;
return 0;
}
std::string ip = argv[1];
TcpSvr ts;
if(!ts.CreateSock())
{
return 0;
}
struct sockaddr_in svraddr;
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(PORT);
svraddr.sin_addr.s_addr = inet_addr(ip.c_str());
if(!ts.Connect(&svraddr))
{
return 0;
}
while(1)
{
std::string msg;
std::cout << "client say:";
fflush(stdout);
std::cin >> msg;
ts.Send(msg);
ts.Recv(msg);
std::cout << "server say:" << msg << std::endl;
}
ts.Close();
return 0;
}