目录
1 预备知识
1.1 端口号
1.2 TCP/UDP
1.3 网络字节序
1.4 sockaddr
2 socket接口
3 UDP简单程序
3.1服务端
3.2 客户端
4 TCP简单程序
4.1服务端
4.2 客户端
在IP数据报头中有着源ip地址和目的ip地址,由此将两个主机联系在了一起。但是在计算机的世界中实际上进行的是进程间的通信,因此我们不仅将两个主机上联系在一起,还需要将两个主机上的进程联系起来。而端口号就是能唯一标识一台主机上的某个进程的标识数据,使用端口号+ip地址就可以实现上述两主机间跨网络的进程通信。
特点:
端口号(port)是一个2字节16位的整数,属于传输层协议的内容。
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程。
一个进程可以绑定多个端口号,但是一个端口号只能被一个进程占用。
套接字一般使用常规传输层的接口,简单了解解一下两个传输层协议。
TCP —— UDP
可靠传输 —— 不可靠传输
有连接 —— 无连接
字节流 —— 数据报
发送端将发送缓冲区的数据按内存地址由低到高发送。接收端同样在接受缓冲区由低到高地址的方式接收。
TCP/IP协议规定发送到网络数据中采用大端字节序,如果是小端机在发送或接收时要进行大小端的转换。
可调用库函数进行主机字节序和网络字节序的转换。
#include
unit32_t htonl(unit32_t hostlong);
unit16_t htons(unit16_t hostshort);
unit32_t ntohl(unit32_t netlong);
unit16_t ntohs(unit16_t netshort);
socket api的接口是sockaddr, 但是在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。
in_addr 表示ipv4 的IP地址
// 创建 socket
int socket(int domain, int type, int protocol);
//返回文件描述符 (种类,类型,协议(固定0))
// 绑定端口号
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (backlog先固定填5)
int listen(int socket, int backlog);
// 接收请求
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
创建套接字>>绑定>>提供服务。
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string port)
{
std::cout << "Usage:\n\t"
<< port << " port" << std::endl;
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
const uint16_t port = atoi(argv[1]);
// 1.创建套接字
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket create fail:" << errno << std::endl;
return 1;
}
// 2.绑定套接字
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
// 云服务器不允许用户直接bind,云服务器有多个ip
// local.sin_addr.s_addr = inet_addr("127.0.0.1");
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
std::cerr << "socket bind fail:" << errno << std::endl;
return 2;
}
// 3.提供服务
bool quit = false;
char buffer[1024];
while (!quit)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer) - 1, 0,
(struct sockaddr *)&peer, &len);
std::cout << "client# " << buffer << std::endl;
if (cnt > 0)
{
buffer[cnt] = 0;
FILE* fp = popen(buffer,"r");
std::string echo_hello = buffer;
char line[1024] = {0};
while(fgets(line,sizeof(line),fp))
{
echo_hello += line;
}
fclose(fp);
sendto(sock, echo_hello.c_str(), echo_hello.size(), 0,
(struct sockaddr *)&peer, len);
}
}
return 0;
}
客户端不需要显示绑定端口,创建套接字后直接对服务端请求服务。
#include
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << "server_ip server_port" << std::endl;
}
//./udp_client server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 0;
}
// 1.创建套接字
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket create fail:" << errno << std::endl;
return 1;
}
//你要给谁发
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
//客户端使用随机端口,由操作系统自动绑定
// 2.使用服务
while (1)
{
// // 输入数据
// std::string message;
// std::cout << "输入#";
// std::cin >> message;
std::cout << "MyShell $";
char line[1024];
fgets(line, sizeof(line), stdin);
sendto(sock, line, strlen(line), 0, (struct sockaddr *)&server, sizeof(server));
struct sockaddr_in tmp;
socklen_t len = sizeof(tmp);
char buffer[1024];
ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&tmp, &len);
if (cnt > 0)
{
buffer[cnt] = 0;
std::cout << "server echo#" << buffer << std::endl;
}
}
return 0;
}
创建套接字>>绑定>>监听>>创建线程提供服务>>每个线程关闭自己的文件描述符。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string proc)
{
std::cout << proc<< " port" << std::endl;
}
void Service(int new_sock)
{
// 提供服务
while (true)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
ssize_t s = read(new_sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
std::cout << "client#" << buffer << std::endl;
std::string echo_string = "server echo#";
echo_string += buffer;
write(new_sock, echo_string.c_str(), echo_string.size());
}
else if (s == 0)
{
std::cout << "client quit ..." << std::endl;
break;
}
else
{
std::cerr << "read fail" << errno << std::endl;
break;
}
}
}
void* Handler(void* args)
{
pthread_detach(pthread_self());
int sock = *(int*)args;
delete (int*)args;
Service(sock);
close(sock);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
// 1创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
{
std::cerr << "socket create:" << errno << std::endl;
return 2;
}
// 2 bind
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
std::cerr << "bind fail" << std::endl;
return 3;
}
// 3 监听
const int back_log = 5;
if (listen(listen_sock, back_log) < 0)
{
std::cerr << "listen fail:" << errno << std::endl;
return 4;
}
signal(SIGCHLD, SIG_IGN); // Linux中父进程忽略子进程的SIGCHLD信号,子进程自动退出释放资源
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
if (new_sock < 0)
continue;
//得到连接的客户信息
uint16_t cli_port = ntohs(peer.sin_port);
std::string cli_ip = inet_ntoa(peer.sin_addr);
std::cout << "get a new link ->" << cli_ip << ":" << cli_port << ":" << new_sock << std::endl;
//多线程版
pthread_t tid;
int* pram = new int(new_sock);
pthread_create(&tid,nullptr,Handler,pram);
// //多进程版
// pid_t id = fork();
// if (id < 0)
// continue;
// else if (id == 0)
// {
// // child
// close(listen_sock);
// Service(new_sock);
// close(new_sock); //防止文件描述符泄露
// exit(0);
// }
// else
// {
// // father do nothing
// close(new_sock);
// }
// //单进程版
// Service(new_sock);
}
return 0;
}
创建套接字后连接服务器,即可请求服务。
#include
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << "server_ip server_port" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 1;
}
std::string svr_ip = argv[1];
uint16_t svr_port = atoi(argv[2]);
// 1 创建socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "socket creat fail" << std::endl;
return 2;
}
// 2 client不用显示bind、listen、accept
// 获取自身信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(svr_ip.c_str());
server.sin_port = htons(svr_port);
// connect
if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
{
std::cerr << "socket connect fail" << std::endl;
return 2;
}
std::cout<<"connect success"< 0)
{
buffer[s] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}