TCP/IP协议族包括运输层、网络层、链路层,而socket所在位置如图,Socket是应用层与TCP/IP协议族通信的中间软件抽象层。
下面是网络socket通信的基本流程:
socket函数
int socket(int domain, int type, int protocol);
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符,它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数
分别为:
domain:即协议域,又称为协议族(family)。常用的协议族有AF_INET(ipv4网络)、AF_INET6(ipv6网络)、AF_LOCAL(本地通信)(或称AF_UNIX,Unix域socket)等等。
type:指定socket类型。常用的socket类型有,
SOCK_STREAM(TCP):流套接字(在某些方面类似域标准的输入/输出流)提供的是一个有序,可靠,双向字节流的连接
SOCK_DGRAM(UDP):数据包套接字不建立和维持一个连接。它对可以发送的数据包的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失,复制或乱序到达
SOCK_RAW、SOCK_PACKET等等。
protocol:是指定协议。常用的协议有,IPPROTO_TCP(TCP)、IPPTOTO_UDP(UDP)、IPPROTO_SCTP(STCP)、IPPROTO_TIPC(TIPC)等,当protocol为0时,会自动选择type类型对应的默认协议。
bind()函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,但最终都会强制转换后赋值给sockaddr这种类型的指针传给内核。
addrlen:对应的是地址的长度。
listen()函数
socket()函数创建的socket默认是一个主动类型的,如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,该函数socket变为被动类型的,等待客户的连接请求。
int listen(int sockfd, int backlog);
sockefd: socket()系统调用创建的要监听的socket描述字;
backlog: 相应socket可以在内核里排队的最大连接个数;
accept()函数
TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定socket地址了。服务器之后就会调用accpet()接受来自客户端的连接请求,这个函数默认是一个阻塞函数,这也意味着如果没有客户端连接服务器的话该程序将一直阻塞着不会返回,直到有一个客户端连过来为止。一旦客户端调用connect()函数就会触发服务器的accept()返回,这时整个TCP链接就建立好
了。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd: 服务器开始调用socket()函数生成的,称为监听socket描述字;
*addr: 用于返回客户端的协议地址,这个地址里包含有客户端的IP和端口信息等;
addrlen: 返回客户端协议地址的长度;
accept函数的返回值是由内核自动生成的一个全新的描述字(fd),代表与返回客户的TCP连接。如果想发送数据给该客户端,则我们可以调用write()等函数往该fd里写内容即可;而如果想从该客户端读内容则调用read()等函数从该fd里读数据即可。
connect(函数)
TCP客户端程序调用socket()创建socket fd之后,就可以调用connect()函数来连接服务器。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd: 客户端的socket()创建的描述字;
addr: 要连接的服务器的socket地址信息,这里面包含有服务器的IP地址和端口等信息;
addrlen: socket地址的长度;
服务端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 12345
int main(int argc, char *argv[]) {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[1024];
// 创建服务器套接字
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("Error creating socket");
exit(EXIT_FAILURE);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
// 绑定并监听
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Error binding");
exit(EXIT_FAILURE);
}
listen(server_socket, 5);
// 接受客户端连接
printf("Server listening on port %d...\n", PORT);
while (true) {
if ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len)) < 0) {
perror("Error accepting");
continue;
}
// 从客户端接收数据
read(client_socket, buffer, sizeof(buffer));
printf("Received from client: %s\n", buffer);
// 关闭并结束连接
close(client_socket);
}
return 0;
}
客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SERVER_IP "127.0.0.1"
#define PORT 12345
int main(int argc, char *argv[]) {
int client_socket;
char message[] = "Hello, Server!";
struct sockaddr_in server_addr;
socklen_t server_len = sizeof(server_addr);
// 创建客户端套接字
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0) {
perror("Error creating socket");
exit(EXIT_FAILURE);
}
// 设置服务器地址
server_addr.sin_family = AF_INET;
inet_aton(SERVER_IP, &server_addr.sin_addr);
server_addr.sin_port = htons(PORT);
// 连接到服务器
if (connect(client_socket, (struct sockaddr *)&server_addr, server_len) < 0) {
perror("Error connecting");
exit(EXIT_FAILURE);
}
// 发送消息到服务器
write(client_socket, message, strlen(message) + 1);
printf("Message sent to server.\n");
// 关闭连接
close(client_socket);
return 0;
}