1.基本原理
- socket即为套接字,在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一的标识网络通讯中的一个进程,“IP地址+TCP或UDP端口号”就为socket。
- 在TCP协议中,建立连接的两个进程(客户端和服务器)各自有一个socket来标识,则这两个socket组成的socket pair就唯一标识一个连接。
- socket本身就有“插座”的意思,因此用来形容网络连接的一对一关系,为TCP/IP协议设计的应用层编程接口称为socket API。
下面来了解下TCP的通信时序:
可以看到客户端是不进行bind的,当客户端没有自己进行bind时,系统随机分配给客户端一个端口号,并且在分配的时候,操作系统会做到不与现有的端口号发生冲突。但如果自己进行bind,客户端程序就很容易出现问题,假设在一个PC机上开启多个客户端进程,如果是用户自己绑定了端口号,必然会造成端口冲突,影响通信。
2.代码实践
下面直接通过程序来学习:
服务端代码:
//
// Created by viets on 2022/4/16.
//
#include "iostream"
#include "sys/socket.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "unistd.h"
int startup(int port, const char* ip_Addr) {
int listenFd = socket(AF_INET, SOCK_STREAM, 0);
if (listenFd < 0) {
std::cout << "Create Socket Return ERROR!" << std::endl;
exit(1);
}
struct sockaddr_in sock_in;
sock_in.sin_family = AF_INET;
sock_in.sin_port = htons(port);
sock_in.sin_addr.s_addr = inet_addr(ip_Addr);
if (bind(listenFd, (struct sockaddr*)(&sock_in), sizeof(sock_in)) < 0) {
std::cout << "Socket Message Bind ERROR!" << std::endl;
exit(2);
}
if (listen(listenFd, 5) < 0) { //允许最大连接数为5
std::cout << "Listen ERROR!" << std::endl;
}
std::cout << "Server StartUP SUCCESS!" << std::endl;
return listenFd;
}
int main() {
int port = 2000;
char* ip_Addr = "0.0.0.0";
int listenFd = startup(port, ip_Addr); //startup创建文件描述符
struct sockaddr_in remote;
socklen_t len = sizeof(struct sockaddr_in);
while (true) {
int n_listenFd = accept(listenFd, (struct sockaddr*)&remote, &len);
if (n_listenFd < 0) {
std::cout << "ACCEPT ERROR!" << std::endl;
continue;
}
printf("Get A Client. IP:%s, PORT:%d\n",inet_ntoa(remote.sin_addr), ntohs(remote.sin_port));
char buf[1024] = {0};
while (true) {
ssize_t _s = read(n_listenFd, buf, sizeof(buf)-1);
if (_s > 0) {
buf[_s] = 0;
printf("Client send message: %s\n", buf);
} else {
printf("Client is quiet!\n");
break;
}
}
}
return 0;
}
上述服务端的代码,实际创建了两个socket返回两个文件描述符,一个是listenFd,其用于承接ip地址及端口的信息;一个是n_listenFd,用于数据的收发。
但是这样实现只能进行单进程通信,也就是说每次只能使一个客户端连接上进行数据通讯,这显然不符合服务器的基本要求。所以可以通过fork函数创建子进程,将监听的socket和数据收发的socket进行分离。代码如下:
//
// Created by viets on 2022/4/17.
//
#include "iostream"
#include "sys/socket.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "unistd.h"
int startup(int port, const char* ip_Addr) {
int listenFd = socket(AF_INET, SOCK_STREAM, 0);
if (listenFd < 0) {
std::cout << "Create Socket Return ERROR!" << std::endl;
exit(1);
}
struct sockaddr_in sock_in;
sock_in.sin_family = AF_INET;
sock_in.sin_port = htons(port);
sock_in.sin_addr.s_addr = inet_addr(ip_Addr);
if (bind(listenFd, (struct sockaddr*)(&sock_in), sizeof(sock_in)) < 0) {
std::cout << "Socket Message Bind ERROR!" << std::endl;
exit(2);
}
if (listen(listenFd, 5) < 0) { //允许最大连接数为5
std::cout << "Listen ERROR!" << std::endl;
}
std::cout << "Server StartUP SUCCESS!" << std::endl;
return listenFd;
}
int main() {
int port = 2000;
char* ip_Addr = "0.0.0.0";
int listenFd = startup(port, ip_Addr);
struct sockaddr_in remote;
socklen_t len = sizeof(struct sockaddr_in);
while (true) {
int messageFd = accept(listenFd, (struct sockaddr*)&remote, &len);
if (messageFd < 0) {
// std::cout << "Accept Socket ERROR!" << std::endl;
continue;
}
std::cout << "Accept Socket Success!" << std::endl;
pid_t pid = fork();
if (pid < 0) {
std::cout << "Create Sub-process FAILED!" << std::endl;
return 1;
} else if (pid == 0) {
//子进程负责信息socket的数据收发
close(listenFd);
char buf[1024];
while (true) {
ssize_t _s = read(messageFd, buf, sizeof(buf));
if (_s > 0) {
std::cout << "Receive Message: "<< buf << std::endl;
} else {
std::cout << "Client is quiet!" << std::endl;
break;
}
}
} else {
//父进程负责监听
close(messageFd);
}
}
return 0;
}
客户端代码:
//
// Created by viets on 2022/4/16.
//
#include "sys/socket.h"
#include "iostream"
#include "stdio.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "unistd.h"
#include "string.h"
int main() {
int port = 2000;
char* ip_Addr = "172.31.105.110";
int clientFd = socket(AF_INET, SOCK_STREAM, 0);
if (clientFd < 0) {
std::cout << "Create Client Socket FAILED!" << std::endl;
return 1;
}
struct sockaddr_in sock_info;
sock_info.sin_family = AF_INET;
sock_info.sin_port = htons(port);
sock_info.sin_addr.s_addr = inet_addr(ip_Addr);
socklen_t len = sizeof(struct sockaddr_in);
if (connect(clientFd, (struct sockaddr*)&sock_info, len) < 0) {
std::cout << "Connect ERROR!" << std::endl;
return 2;
}
std::cout << "Connect SUCCESS!"<< std::endl;
char* buf = "Hello Server!\n";
std::cout << strlen(buf) << std::endl;
while (true) {
std::cout << "Sending: " << buf << std::endl;
int len = write(clientFd, buf, strlen(buf));
sleep(2);
if (len < 0) {
std::cout << "Message Send ERROR!" << std::endl;
break;
}
}
close(clientFd);
return 0;
}