socket 函数

在C++中,socket()函数用于创建一个新的网络套接字,它通常包含两个主要参数:域(domain)和类型(protocol)。这两个参数通常作为整数传递:

  1. 域名(Domain):

    • AF_INETAF_INET6: 表示Internet协议(IPv4或IPv6)。这是最常见的,IPv4用AF_INET表示。

    • AF_UNIX (仅限于Unix系统): 使用本地文件系统路径名通信。

  2. 类型(Protocol):

    • SOCK_STREAMIPPROTO_TCP: 对于TCP连接,这个参数通常设置为流式套接字,代表可靠的数据传输,如HTTP。

    • SOCK_DGRAMIPPROTO_UDP: 对于UDP连接,这是一个数据报套接字,代表不可靠但更快的通信,如DNS查询。

    • SOCK_RAW 或其他特定值: 如果需要访问底层网络协议(如ICMP、ARP等),可以使用原始套接字。

创建套接字的基本语法类似于:

int socket_type = SOCK_STREAM; // 或 SOCK_DGRAM
int domain = AF_INET; // 或 AF_INET6
int socket_fd = socket(domain, socket_type, 0);

在C++中,当你调用socket()函数时,第三个参数通常设置为0。这其实是一个可选参数,用于指定套接字选项(socket options)的初始化列表。然而,在大多数情况下,如果省略这个参数,系统会使用默认的行为或配置。

0在这里并不是一个特殊选项,而是表示不提供初始选项。如果你想要应用特定的套接字选项,可以使用setsockopt()函数传递一个结构体,其中包含了选项名称、值和选项长度。在不清楚具体需求的情况下,默认值0表示不立即设置任何选项,你可以之后再通过setsockopt()来添加。

有时候,0也可能代表对特定版本的支持或者兼容性处理。但总的来说,对于新手来说,如果不明确需要自定义选项,通常可以直接忽略第三个参数,因为很多情况下socket()函数不需要额外的初始化设置。

/* Create a new socket of type TYPE in domain DOMAIN, using
   protocol PROTOCOL.  If PROTOCOL is zero, one is chosen automatically.
   Returns a file descriptor for the new socket, or -1 for errors.  */
extern int socket (int __domain, int __type, int __protocol) __THROW;

bind函数

bind()是网络编程中的一个核心函数,它通常在创建监听套接字(如TCP服务器端口)时使用,用于将套接字绑定到一个具体的IP地址和端口号上。其基本语法如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

  • sockfd: 表示已经创建好的套接字描述符(socket file descriptor)。

  • addr: 指向一个结构体地址,通常是struct sockaddr_in(IPv4)或struct sockaddr_in6(IPv6),包含了要绑定的IP地址和端口号信息。

  • addrlen: 结构体addr的实际大小。

bind()函数成功执行后,套接字就被固定到了指定的IP地址和端口,等待连接请求。这对于服务端应用程序来说非常重要,因为它确定了服务器将在何处监听客户端的连接。

需要注意的是,bind()之前通常需要先调用listen()函数开启监听,然后通过accept()接收新连接。

#include<sys/socket.h>
#include<arpa/inet.h>
void test() {
	system("clear");
	int sock = socket(PF_INET,SOCK_STREAM,0);
	if (sock != -1) { //表示创建套接字成功
		struct sockaddr_in addr;
		addr.sin_family = AF_INET;//填写地址的协议族
		addr.sin_addr.s_addr = inet_addr("192.168.0.1");//转化字符串ip地址
		addr.sin_port = htons(3344);//端口 u_int16

//int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)
//   # define __SOCKADDR_ARG		struct sockaddr *__restrict
		int ret = bind(sock,(sockaddr*)&addr,sizeof(addr));
//成功返回 0 失败返回 -1
		if (bind != 0) {
			// 出错
		}
	}
}

listen & accept

#include<sys/socket.h>

listen 函数

int listen(int sock,int backlog);
//成功返回 0 失败返回 -1

参数:

backlog:连接请求等待队列(Queue)的长度,若为5,则队列长度为5,表示最多使5个连接请求进入队列。

accept 函数

int accept(int socket,struct sockaddr* addr,socklen_t* addrlen);//会阻塞
//返回客户端套接字 描述符 失败返回 -1

TCP 编程

TCP服务端代码实现

tcp 服务器创建 基本流程

  1. socket() 创建套接字

  2. bind() 分配套接字地址

  3. listen() 等待连接请求状态

  4. accept() 允许连接

  5. read()/write() 数据交换

  6. close() 断开连接

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<iostream>

void error_handling(char* message);

void tcp_server() {
	//1.创建两个套接字变量
	int server_sock{};
	int client_sock{};

	//2.两个绑定的地址
	typedef struct sockaddr_in sockAddr_in;
	sockAddr_in server_addr{};
	sockAddr_in client_addr{};

	//创建客户端地址的长度
	socklen_t client_addr_size{};

	//创建一个当连接 接收到时的回文
	const char* r_to_c_message = "hello world!\n";

	//3.创建服务器套接字
	//tcp 协议 面向 (链接/流) 的传输协议
	server_sock = socket(PF_INET, SOCK_STREAM, 0);

	//若 创建失败
	if (server_sock == -1) {
		std::cout << "creat socket failed !" << std::endl;
	}

	//4.绑定地址
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	server_addr.sin_port = htons(2345);
	int ret_bind = bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if (ret_bind == -1) {
		std::cout << "bind failed!\n" << std::endl;
		close(server_sock);
		return;
	}

	//5.listen()
	int ret_listen = listen(server_sock, 3);
	if (ret_listen == -1) {
		std::cout << "listen failed!\n" << std::endl;
		close(server_sock);
		return;
	}

	//6.accept()
	client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_size);
	if (client_sock == -1) {
		std::cout << "client_sock accept failed!\n" << std::endl;
		close(server_sock);
		return;
	}

	//7.send message
	ssize_t ret_write = write(client_sock,r_to_c_message,strlen(r_to_c_message));
	if (ret_write != strlen(r_to_c_message)) {
		std::cout << "ret_write failed!\n" << std::endl;
		close(server_sock);
		return;
	}
	//close(client_sock); 可以不执行
	close(server_sock); //服务器关闭的时候,客户端会自动关闭


}

int main(int argc, char* argv[]) {
	return 0;
}

void error_handling(char* message) {
	printf("错误处理\n");
}

connect函数

int connect(int client_sock,struct sockaddr* servaddr,socklen_t addrlen);
//客户端套接字描述符
//mu'bioa目标服务器端地址信息和bian变量地址值     
//地址长度

//成功返回0 否则-1

客户端套接字地址信息在哪?

创建套接字后立即调用connet函数,操作系统在内核中 根据计算机ip和端口在调用connect函数时自动分配,无需用标记的bind函数进行分配

TCP客户端代码实现及其联调

流程:

  1. socket() 创建套接字

  2. connect() 请求连接

  3. r/w 交换数据

  4. close() 断开连接

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<iostream>

void error_handling(char* message);

void tcp_server() {
	//1.创建两个套接字变量
	int server_sock{};
	int client_sock{};

	//2.两个绑定的地址
	typedef struct sockaddr_in sockAddr_in;
	sockAddr_in server_addr{};
	sockAddr_in client_addr{};

	//创建客户端地址的长度
	socklen_t client_addr_size{};

	//创建一个当连接 接收到时的回文
	const char* r_to_c_message = "hello world!\n";

	//3.创建服务器套接字
	//tcp 协议 面向 (链接/流) 的传输协议
	server_sock = socket(PF_INET, SOCK_STREAM, 0);

	//若 创建失败
	if (server_sock == -1) {
		std::cout << "creat socket failed !" << std::endl;
	}

	//4.绑定地址
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
	server_addr.sin_port = htons(2345);
	int ret_bind = bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if (ret_bind == -1) {
		std::cout << "bind failed!\n" << std::endl;
		close(server_sock);
		return;
	}

	//5.listen()
	int ret_listen = listen(server_sock, 3);
	if (ret_listen == -1) {
		std::cout << "listen failed!\n" << std::endl;
		close(server_sock);
		return;
	}
	std::cout << "listen is over!" << std::endl;
	//6.accept()
	client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_size);
	if (client_sock == -1) {
		std::cout << "client_sock accept failed!\n" << std::endl;
		close(server_sock);
		return;
	}
	std::cout << "accept is ok" << std::endl;
	//7.send message
	ssize_t ret_write = write(client_sock,r_to_c_message,strlen(r_to_c_message));
	if (ret_write != (ssize_t)strlen(r_to_c_message)) {
		std::cout << "ret_write failed!\n" << std::endl;
		close(server_sock);
		return;
	}
	//close(client_sock); 可以不执行
	close(server_sock); //服务器关闭的时候,客户端会自动关闭
}

void tcp_client() {
	int client_socket = socket(PF_INET, SOCK_STREAM, 0);
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	servaddr.sin_port = htons(2345);
	int ret_connect = connect(client_socket, (struct sockaddr*)&servaddr, sizeof(servaddr));
	if (ret_connect == 0) {
		char buffer[256]{ "" };
		read(client_socket, buffer, sizeof(buffer));
		std::cout << "client: " << buffer << std::endl;
		close(client_socket);
	}
	std::cout << "client done .. " << std::endl;
}

void runTCP() {
	pid_t pid = fork();
	if (pid == 0) { //开启子进程成功
		sleep(1); //先等待服务器启动成功
		tcp_client();

	}
	else if(pid > 0){//主进程
		//调用服务器启动
		tcp_server();
		std::cout << "server done" << std::endl;
		int status = 0;
		wait(&status); //避免出现僵尸进程
	}
	else {
		std::cout << "fork failed!"<<std::endl;
	}
}

int main(int argc, char* argv[]) {
	runTCP();
	return 0;
}

void error_handling(char* message) {
	printf("错误处理\n");
}

回声服务器

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<iostream>

void error_handling(char* message);

void tcp_server() {
	//1.创建两个套接字变量
	int server_sock{};
	int client_sock{};

	//2.两个绑定的地址
	typedef struct sockaddr_in sockAddr_in;
	sockAddr_in server_addr{};
	sockAddr_in client_addr{};

	//创建客户端地址的长度
	socklen_t client_addr_size{};

	//3.创建服务器套接字
	//tcp 协议 面向 (链接/流) 的传输协议
	server_sock = socket(PF_INET, SOCK_STREAM, 0);

	//若 创建失败
	if (server_sock == -1) {
		std::cout << "creat socket failed !" << std::endl;
	}

	//4.绑定地址
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//要监听的地址(设置为全网络接口监听)
	server_addr.sin_port = htons(2345);
	int ret_bind = bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if (ret_bind == -1) {
		std::cout << "bind failed!\n" << std::endl;
		close(server_sock);
		return;
	}

	//5.listen()
	int ret_listen = listen(server_sock, 3);
	if (ret_listen == -1) {
		std::cout << "listen failed!\n" << std::endl;
		close(server_sock);
		return;
	}
	//std::cout << "listen is over!" << std::endl;

	//std::cout << "accept is ok" << std::endl;
	//7.send message
	char buffer[1024];
	
	for (int i = 0; i < 2; i++) {
		//6.accept()
		client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_size);
		if (client_sock == -1) {
			std::cout << "client_sock accept failed!\n" << std::endl;
			close(server_sock);
			return;
		}
		memset(buffer, 0, sizeof(buffer));
		//ssize_t len =  read(client_sock, buffer, sizeof(buffer));
		ssize_t len = 0;
		while ((len = read(client_sock, buffer, sizeof(buffer))) > 0) {
			len = write(client_sock, buffer, len);
			if (len != (ssize_t)strlen(buffer)) {
				std::cout << "ret_write failed!\n" << std::endl;
				close(server_sock);
				return;
			}
			memset(buffer, 0, sizeof(buffer));
		}
		if (len <= 0) { //说明空字符串
			std::cout << "read failed!" << std::endl;
			close(server_sock);
			return;
		}
		
		close(client_sock);
	}

	//close(client_sock); 可以不执行
	close(server_sock); //服务器关闭的时候,客户端会自动关闭
}

void tcp_client() {
	int client_socket = socket(PF_INET, SOCK_STREAM, 0);
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET; //设置Net
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //要发送服务器的ip地址
	servaddr.sin_port = htons(2345);
	int ret_connect = connect(client_socket, (struct sockaddr*)&servaddr, sizeof(servaddr));
	while(ret_connect == 0) {
		char buffer[256]{ "" };
		fputs("send message\"(Q/q quit)\"):",stdout);
		fgets(buffer,sizeof(buffer),stdin);
		if ((strcmp(buffer, "Q\n") == 0) || (strcmp(buffer, "q\n") == 0)) {
			break;
		}

		size_t len = strlen(buffer);
		size_t send_len{ 0 };
		while (send_len < len) {
			ssize_t ret = write(client_socket, buffer+send_len, len - send_len); //向服务器分包发送数据
			if (ret <= 0) {
				fputs("write failed!\n",stdout);
				close(client_socket);
				std::cout << "client done!"<<std::endl;
				break;
			}
			send_len += (size_t)ret;
		}
		memset(buffer,0,sizeof(buffer));

		size_t read_len = 0;
		while (read_len < len) {
			ssize_t ret = read(client_socket, buffer+read_len, len-read_len); //接收数据
			if (ret <= 0) {
				fputs("read failed!\n", stdout);
				close(client_socket);
				std::cout << "client done!" << std::endl;
				break;
			}
			read_len += (size_t)ret;
		}
		std::cout << "from server: " << buffer << std::endl;
	}
	//printf("%s(%d):%s %d\n",__FILE__,__LINE__,__FUNCTION__, ret_connect);

	close(client_socket);
	std::cout << "client done!" << std::endl;
}

void runTCP() {
	pid_t pid = fork();
	if (pid == 0) { //开启子进程成功
		tcp_server();
	}
	else if (pid > 0) {//主进程
		//调用客户端启动
		for(int i = 0;i < 2;++ i)
			tcp_client();
		int status = 0;
		wait(&status); //避免出现僵尸进程
	}
	else {
		std::cout << "fork failed!" << std::endl;
	}
}

int main(int argc, char* argv[]) {
	runTCP();
	return 0;
}

void error_handling(char* message) {
	printf("错误处理\n");
}

UDP回声服务器实现

oid UDP_server(int argc,char* argv[]) {
	int server_scoket{ -1 };
	char message[512]{""};
	struct sockaddr_in server_addr,client_addr;
	socklen_t clientLen{ 0 };

	if (argc != 2) {
		printf("usage:% <port>\n",argv[0]);
		error_handling("argument is error:");
	}
	server_scoket = socket(PF_INET,SOCK_DGRAM,0);//UDP

	if (server_scoket == -1) {
		error_handling("create socket failed");
	}
	memset(&server_addr,0,sizeof(server_addr));

	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
	server_addr.sin_port = htons((short)atoi(argv[1]));

	if (bind(server_scoket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
		error_handling("bind failed");
	}

	//printf("服务器绑定成功!\n");

	int _nums = 10;
	while (_nums--) {
		//printf("server开始接收数据\n");
		clientLen = sizeof(client_addr);
		ssize_t len = recvfrom(server_scoket,message,sizeof(message),0,
			(struct sockaddr*)&client_addr, &clientLen);
		printf("server开始接收成功:%s\n",message);
		if (sendto(server_scoket, message, strlen(message), 0, (struct sockaddr*)&client_addr, clientLen) == -1) {
			printf("server发送失败!\n");
		}
		else {
			printf("server发送成功\n");
		}

		memset(message,0,len);
	}

	close(server_scoket);
}

int UDP_client(int argc,char* argv[]) {
	int client_socket{};
	struct sockaddr_in server_addr;
	socklen_t server_len{0};
	char message[512]{""};

	if (argc != 3) {
		printf("usage:%s is port\n",argv[0]);
		error_handling("argument error!");
	}

	client_socket = socket(PF_INET,SOCK_DGRAM,0);

	if (client_socket == -1) {
		error_handling("socket falied");
	}

	memset(&server_addr,0,sizeof(server_addr));

	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);
	server_addr.sin_port = htons((short)atoi(argv[2]));

	while (1) {
		printf("input message(q/Q):");
		fgets(message,sizeof(message),stdin);
		if (strcmp(message, "q\n") == 0 || strcmp(message, "Q\n") == 0
			|| strcmp(message, "q") == 0 || strcmp(message, "Q") == 0) {
			printf("退出成功!\n");
			break;
		}
		ssize_t len = sendto(client_socket,message,strlen(message),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
		if (len == -1) {
			std::cout << "sendto failed"<<std::endl;
			return -1;
		}
		memset(message,0,sizeof(message));
		recvfrom(client_socket,message,sizeof(message),0,(struct sockaddr*)&server_addr, &server_len);
		printf("recv:%s\n",message);
	}

	close(client_socket);
	return 0;
}


void runUDP(char* argv0) {
	if (fork() > 0) {
		int argc = 3;
		char* argv[] = { argv0,(char*)"127.0.0.1",(char*)"2233" };
		UDP_client(argc, argv);
		int status = 0;
		wait(&status);
	}
	else {
		int argc = 2;
		char* argv[] = { argv0,(char*)"2233" };
		UDP_server(argc, argv);
	}
}

int main(int argc, char* argv[]) {
	//runTCP();
	runUDP(argv[0]);
	return 0;
}

void error_handling(const char* message) {
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}