【概述】
套接字(Socket)是一种用于网络中不同主机上的应用进程之间进行双向通信的端点的抽象。以下是关于套接字的详细信息:
套接字的基本概念
-
定义:
- 套接字是对网络上不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
- 套接字最早应用于UNIX系统,所有操作均是面向文件的,即Socket的通信模式也是基于文件操作的。
-
作用:
- 套接字屏蔽了各个协议的通信细节,提供了TCP/IP协议的抽象,对外提供了一套接口,程序员可以通过这些接口统一、方便地使用TCP/IP协议的功能。
- 它是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。
套接字的类型
-
流套接字(Stream Socket):
- 使用TCP提供可靠的字节流服务。适用于需要可靠连接的场景,如网页浏览、邮件传输等。
-
数据报套接字(Datagram Socket):
- 使用UDP提供“尽力而为”的数据报服务。适用于对实时性要求较高,但可以容忍数据丢失的场景,如视频会议、在线游戏等。
套接字的应用场景
- 网络编程:套接字是网络编程的基础,广泛应用于客户端-服务器模型中的各种网络应用,如Web服务器、邮件服务器、文件传输等12。
- 跨平台通信:套接字提供了一种统一的接口,使得不同平台上的应用程序可以通过网络进行通信
【流套接字】
流式套接字(TCP)在网络编程中使用的接口主要包括以下几个:
socket
创建套接字:使用socket()
函数创建一个套接字
int socket(int domain, int type, int protocol);
domain
:套接字的地址族,例如AF_INET
(IPv4)。type
:套接字类型,对于TCP流式套接字使用SOCK_STREAM
。protocol
:通常设为0,系统会自动选择合适的协议。
bind
绑定套接字:使用bind()
函数将套接字绑定到一个地址和端口上
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
:套接字文件描述符。addr
:指向sockaddr
结构的指针,包含地址和端口信息。addrlen
:addr
结构的大小。
connect
建立连接:使用connect()
函数建立连接(客户端)
connect()
函数是套接字编程中的一个重要函数,用于建立与远程主机的连接。它在客户端套接字上调用,以请求与服务器套接字建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
sockfd
:套接字描述符,即通过socket()
函数创建的套接字。 -
addr
:指向sockaddr
结构的指针,该结构包含了服务器的地址信息,包括IP地址和端口号。 -
addrlen
:addr
结构的大小。
返回值
- 如果连接成功,
connect()
返回0。 - 如果连接失败,返回-1,并设置相应的错误码(可以通过
errno
获取)
错误码
一些常见的错误码包括:
ECONNREFUSED
:目标主机拒绝连接。ETIMEDOUT
:连接超时。ENETUNREACH
:网络不可达。EADDRINUSE
:地址已被使用。ECONNRESET
:连接被重置。
示例
以下是一个使用connect()
函数的简单示例,展示了如何连接到本地主机的12345端口:
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
int main() {
int sockfd;
struct sockaddr_in servaddr;
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 设置服务器地址
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(12345);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 连接服务器
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("connect failed");
return 1;
}
printf("Connected to server\n");
// 关闭套接字
close(sockfd);
return 0;
}
在这个示例中,我们首先创建了一个套接字,然后设置了服务器的地址和端口,最后调用connect()
函数尝试建立连接。如果连接成功,程序将打印"Connected to server";如果连接失败,将打印错误信息。
注意事项
- 在调用
connect()
之前,套接字应该已经通过socket()
函数创建。 connect()
通常用于TCP套接字,因为TCP是面向连接的协议。- 对于UDP套接字,
connect()
也可以使用,但它不会建立实际的连接,而是设置默认的目的地址,以便后续的send()
和recv()
调用可以省略地址参数。
connect()
函数是网络编程中的基本操作之一,用于实现客户端与服务器之间的连接建立。
listen
在流套接字接口中,listen()
函数扮演着重要的角色,主要用于服务器端程序,
listen()
函数用于将一个套接字设置为被动监听状态,以便接收客户端的连接请求。这是服务器端程序在绑定套接字后必须执行的一步,之后才能使用 accept()
函数来响应客户端的请求
int listen(int sock, int backlog); // Linux
int listen(SOCKET sock, int backlog); // Windows
sock
:需要进入监听状态的套接字。backlog
:请求队列的最大长度,即可以等待处理的最大连接请求数。
工作原理
-
被动监听:
- 当没有客户端请求时,套接字处于“睡眠”状态。
- 只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
-
请求队列:
- 如果套接字正在处理客户端请求时,有新的请求进来,套接字会将其放入缓冲区(请求队列)。
- 待当前请求处理完毕后,再从缓冲区中读取新的请求进行处理。
- 请求队列按照先来先服务的原则排队,直到缓冲区满。
示例
在服务器端网络编程中,通常按以下步骤使用 listen()
函数:
- 创建套接字:
socket()
- 绑定套接字:
bind()
- 设置监听:
listen()
- 接受连接:
accept()
注意事项
- backlog 参数:指定了请求队列的最大长度,即最多可以有多少个客户端连接请求在等待处理。如果队列满了,新的连接请求可能会被拒绝。
- 套接字状态:
listen()
函数会将套接字从主动状态转换为被动状态,准备接收连接请求。
accept
套接字接口中的accept
函数是用于TCP服务器的一个关键函数,它用于从已完成连接的队列中取出一个连接,如果这个队列没有已完成连接,那么accept
函数会阻塞,直到取出一个连接。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明
sockfd
:套接字描述符,这个套接字应该是之前已经通过socket
函数创建,并通过bind
和listen
函数设置为监听状态的。addr
:这是一个指向sockaddr
结构的指针,用于返回连接客户端的地址信息。这个参数可以是NULL
,如果不关心客户端的地址信息。addrlen
:这是一个指向socklen_t
类型的指针,用于传入addr
结构的大小,同时函数返回时,这个变量会被设置为实际返回的地址信息的大小。
返回值
- 如果成功,
accept
函数返回一个新的套接字描述符,这个描述符用于与客户端进行通信。 - 如果失败,返回
-1
,并设置相应的错误码。
注意事项
accept
函数通常在循环中使用,以便服务器可以接受多个客户端的连接。- 返回的新套接字描述符不会继承原套接字描述符的属性,例如非阻塞或异步等。
- 在多线程服务器中,通常每个连接会创建一个新线程来处理,这样可以提高服务器的并发处理能力。
示例代码
以下是一个简单的TCP服务器示例,使用accept
函数接受客户端连接:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 绑定地址和端口
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 通信(略)
close(new_socket);
close(server_fd);
return 0;
}
在这个示例中,服务器创建了一个套接字,绑定到本地地址和端口,然后监听连接。使用accept
函数接受客户端的连接,并返回一个新的套接字描述符用于通信。最后,关闭套接字描述符。
send
流套接字接口中的send
函数用于在TCP连接上发送数据。它是套接字编程中的一个重要函数,用于将数据从本地发送到连接的对方。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数说明
sockfd
:套接字描述符,表示已经建立连接的套接字。buf
:指向要发送数据的缓冲区的指针。len
:要发送的数据长度,以字节为单位。flags
:控制发送行为的标志,通常设置为0,也可以是以下选项的组合:MSG_CONFIRM
:用于SOCK_DGRAM套接字,提示路由器该数据包是有效的。MSG_DONTROUTE
:绕过路由表查找,直接发送到本地网络。MSG_DONTWAIT
:非阻塞发送,如果没有立即发送数据,则返回EWOULDBLOCK错误。MSG_EOR
:标记消息的结束。MSG_MORE
:提示发送方还有更多数据要发送。MSG_NOSIGNAL
:禁止发送SIGPIPE信号。
返回值
- 如果成功,
send
函数返回实际发送的字节数,这个值可能小于len
,表示数据被部分发送。 - 如果失败,返回
-1
,并设置相应的错误码。
注意事项
send
函数在阻塞模式下会等待直到所有数据都被发送或遇到错误。- 在非阻塞模式下,如果发送缓冲区已满,
send
可能会返回EWOULDBLOCK错误。 - TCP是面向流的协议,
send
函数发送的数据可能会被合并或分割,接收方可能不会按相同的分组接收数据。 - 如果连接被对方关闭,
send
会返回0。
示例代码
以下是一个简单的TCP客户端示例,使用send
函数发送数据:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
int sockfd;
struct sockaddr_in servaddr;
// 创建套接字
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
// 填充服务器信息
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8080);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 连接服务器
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("connection failed");
exit(EXIT_FAILURE);
}
const char *message = "Hello, Server!";
size_t len = strlen(message);
// 发送数据
if (send(sockfd, message, len, 0) < 0) {
perror("send failed");
exit(EXIT_FAILURE);
}
printf("Message sent to server\n");
close(sockfd);
return 0;
}
在这个示例中,客户端创建了一个套接字,连接到本地服务器(假设服务器运行在127.0.0.1的8080端口上),然后使用send
函数发送一个简单的消息。发送完成后,关闭套接字。
recv
流套接字接口中的recv
函数用于从TCP连接上接收数据。它是套接字编程中的一个重要函数,用于从已连接的套接字中读取数据。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数说明
sockfd
:套接字描述符,表示已经建立连接的套接字。buf
:指向接收数据缓冲区的指针,用于存储接收到的数据。len
:缓冲区的长度,即最大可接收的数据长度,以字节为单位。flags
:控制接收行为的标志,通常设置为0,也可以是以下选项的组合:MSG_DONTWAIT
:非阻塞接收,如果没有数据可读,则返回EWOULDBLOCK错误。MSG_ERRQUEUE
:接收错误信息。MSG_OOB
:接收带外数据。MSG_PEEK
:窥视数据,即查看数据但不从接收缓冲区中移除。MSG_TRUNC
:返回未截断的数据报长度。MSG_WAITALL
:等待直到接收到足够的数据填满缓冲区。
返回值
- 如果成功,
recv
函数返回实际接收的字节数。如果连接被对方关闭,返回0。 - 如果失败,返回
-1
,并设置相应的错误码。
注意事项
recv
函数在阻塞模式下会等待直到有数据可读或遇到错误。- 在非阻塞模式下,如果没有数据可读,
recv
可能会返回EWOULDBLOCK错误。 - TCP是面向流的协议,
recv
函数可能会接收合并或分割的数据,具体取决于发送方的发送方式和网络状况。 - 如果对方关闭了连接,
recv
会返回0,表示没有更多数据可读。
示例代码
以下是一个简单的TCP服务器示例,使用recv
函数接收数据:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
ssize_t valread;
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 绑定地址和端口
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 接收数据
valread = recv(new_socket, buffer, BUFFER_SIZE, 0);
if (valread < 0) {
perror("recv failed");
exit(EXIT_FAILURE);
}
printf("Received message: %s\n", buffer);
// 关闭套接字
close(new_socket);
close(server_fd);
return 0;
}
在这个示例中,服务器创建了一个套接字,绑定到本地端口8080,然后监听连接。当接收到客户端连接时,使用accept
函数接受连接,并使用recv
函数从客户端接收数据。接收完成后,关闭新接受的套接字和服务器套接字。
close
流套接字接口中的close
函数用于关闭一个套接字描述符,释放与之相关的资源。在套接字编程中,当完成数据传输后,通常需要调用close
函数来关闭套接字,以终止TCP连接。
int close(int sockfd);
参数说明
sockfd
:要关闭的套接字描述符。
返回值
- 如果成功,
close
函数返回0。 - 如果失败,返回
-1
,并设置相应的错误码。
注意事项
- 调用
close
函数后,套接字描述符将不再有效,无法用于后续的读写操作。 - 在TCP连接中,
close
函数会触发四次挥手(four-way handshake)过程,以优雅地关闭连接。 - 如果套接字上有未完成的数据传输,
close
可能会阻塞,直到所有数据都发送完毕或超时。 - 在多进程或多线程环境中,每个进程或线程都应该关闭它自己的套接字描述符副本。
- 如果套接字设置了
SO_LINGER
选项,close
的行为可能会受到影响,具体取决于SO_LINGER
的设置。
示例代码
以下是一个简单的TCP客户端示例,使用close
函数关闭套接字:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char *hello = "Hello from client";
char buffer[BUFFER_SIZE] = {0};
ssize_t valread;
// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation error");
exit(EXIT_FAILURE);
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT);
// 将地址从字符串转换为二进制形式
if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
perror("Invalid address/ Address not supported");
exit(EXIT_FAILURE);
}
// 连接到服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Connection Failed");
exit(EXIT_FAILURE);
}
// 发送数据
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 接收数据
valread = read(sock, buffer, BUFFER_SIZE);
printf("%s\n", buffer);
// 关闭套接字
close(sock);
return 0;
}
在这个示例中,客户端创建了一个套接字,连接到服务器,发送一条消息,并接收服务器的响应。完成数据传输后,客户端调用close
函数关闭套接字,终止TCP连接。
关闭套接字的替代方法
除了close
函数,还可以使用shutdown
函数来关闭套接字。shutdown
函数允许更细粒度地控制套接字的关闭行为,例如可以指定关闭读操作、写操作或两者都关闭。shutdown
函数的原型如下:
int shutdown(int sockfd, int how);
how
参数可以取以下值:SHUT_RD
:关闭读操作,不再接收数据。SHUT_WR
:关闭写操作,不再发送数据。SHUT_RDWR
:同时关闭读和写操作。
shutdown
函数在需要半关闭套接字(即只关闭一个方向的数据传输)时非常有用。
【testcode】
服务端(server)
server_sock.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <signal.h>
int main(int argc, char **argv)
{
int server_sockfd = -1;
int client_sockfd = -1;
socklen_t client_len = 0;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
/*1.创建流套接字*/
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
/*2.设置服务器接收的链接地址和监听的端口*/
server_addr.sin_family = AF_INET; //指定网络套接字
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//接受所有IP地址的链接
server_addr.sin_port = htons(9736); //绑定到9736端口
/*3.绑定(命名)套接字*/
bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
/*4.创建套接字队列,监听套接字*/
listen(server_sockfd, 5);
/*5.忽略子进程停止或退出信号*/
signal(SIGCHLD, SIG_IGN);
while(1)
{
char ch = '\0';
client_len = sizeof(client_addr);
printf("Server waiting\n");
//接收链接,创建新的套接字
client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &client_len);
if (fork() == 0)
{
//子进程中,读取客户端发过来的信息,处理信息,再发送给客户端
read(client_sockfd, &ch, 1);
sleep(5);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
exit(0);
}
else
{
//父进程中,关闭套接字
close(client_sockfd);
}
}
}
客户端(client)
client_sock.c
#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char** argv)
{
int sockfd = -1;
int len = 0;
struct sockaddr_in address;
int result;
char ch = 'A';
//1.创建流套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
//2.设置要链接的服务器的信息
address.sin_family = AF_INET; //使用网络套接字
address.sin_addr.s_addr = inet_addr("127.0.0.1");//服务器地址
address.sin_port = htons(9736); //服务器所监听的端口
len = sizeof(address);
//3.链接到服务器
result = connect(sockfd, (struct sockaddr *)&address, (socklen_t)len);
if(result == -1)
{
perror("ops: client\n");
exit(1);
}
//4.发送请求给服务器
write(sockfd, &ch, 1);
//5.从服务器获取数据
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
exit(0);
}
运行解雇:
在本例中,先运行server端,接着间接运行两个client端,从运行的结果来看,客户端发送给服务器程序的所有请求都得到了处理,即把A变成了B。对于服务器和客户程序之间使用的read和write系统调用跟使用命名管道时阻塞的read、write系统调用一样。例如客户程序调用read时,如果服务器程序没有向指定的客户程序的socket中写入信息,则read调用会一直阻塞。
流式套接字很像命名管道,但是它却可以使不在同一台计算机而通过网络连接的不同计算机上的进程进行通信,功能真是非常的强大。
【数据报套接字】
【概述】
socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。
相对于流套接字,数据报套接字的使用更为简单,它是由类型SOCK_DGRAM指定的,它不需要建立连接和维持一个连接,它们在AF_INET中通常是通过UDP/IP协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高,因为它并一需要总是要建立和维持一个连接。
数据报套接字(Datagram Socket)是计算机网络编程中的一种套接字类型,主要用于在网络上发送和接收数据报文。数据报套接字支持无连接的网络服务,使用用户数据报协议(UDP,User Datagram Protocol)进行通信。以下是数据报套接字的一些关键特点:
特点:
-
无连接:数据报套接字不需要在通信双方之间建立连接。每个数据报文都是独立发送的,不需要其他报文的上下文。
-
不可靠传输:UDP 不保证数据报文的可靠传输。数据报可能会丢失、重复或到达顺序错误。
-
面向报文:UDP 保留报文的边界,即发送端发送的每个数据报文都会被完整地接收。
-
无拥塞控制:UDP 没有内置的拥塞控制机制,发送端不会根据网络状况调整发送速率。
-
支持广播和多播:数据报套接字可以用于向网络中的所有设备或一组设备发送数据。
使用场景:
- 实时应用:如视频会议、在线游戏等,这些应用对实时性要求高,可以容忍一定的数据丢失。
- 网络诊断工具:如ping和traceroute,它们使用UDP数据报来测试网络连接。
- 流媒体传输:虽然TCP更适合流媒体传输,但某些情况下UDP可以用来传输实时流媒体数据,因为它可以减少延迟。
【工作原理】
使用数据报socket进行进程通信的进程采用的客户/服务器系统是如何工作的呢?
1、服务器端
与使用流套接字一样,首先服务器应用程序用系统调用socket()来创建一个套接安,它是系统分配给该服务器进程的类似文件描述符的资源,它不能与其他的进程共享。
接下来,服务器进程会给套接字起个名字(监听),我们使用系统调用bind()来给套接字命名。然后服务器进程就开始等待客户连接到这个套接字。
不同的是,然后系统调用recvfrom()来接收来自客户程序发送过来的数据。服务器程序对数据进行相应的处理,再通过系统调用sendto()把处理后的数据发送回客户程序。
与流套接字程序相比:
- 在流套接字中的程序中,接收数据是通过系统调用read,而发送数据是通过系统调用write()来实现,而在数据报套接字程序中,这是通过recvfrom()和sendto()调用来实现的。
- 使用数据报套接字的服务器程序并不需要listen()调用来创建一个队列来存储连接,也不需要accept()调用来接收连接并创建一个新的socket描述符
2、客户端
基于数据报socket的客户端比服务器端简单,同样,客户应用程序首先调用socket来创建一个未命名的套接字,与服务器一样,客户也是通过sendto()和recvfrom()来向服务器发送数据和从服务器程序接收数据。
与流套接字程序相比:
使用数据报套接字的客户程序并不需要使用connect()系统调用来连接服务器程序,它只要在需要时向服务器所监听的IP端口发送信息和接收从服务器发送回来的数据即可。
数据报套接字(Datagram Sockets)通常用于 UDP(用户数据报协议)通信。以下是一些常用的数据报套接字 API 函数,这些函数在 POSIX 兼容的系统(如 Linux、BSD、Mac OS X)中使用,并且通常在 <sys/types.h>和<sys/socket.h>
头文件中声明:
数据报套接字(UDP)在网络编程中使用的接口主要包括以下几个:
socket
创建套接字
int socket(int domain, int type, int protocol);
domain
: 地址族,对于 Internet 地址,通常是AF_INET
(IPv4)或AF_INET6
(IPv6)。type
: 套接字类型,对于数据报套接字,使用SOCK_DGRAM
。protocol
: 通常设置为 0,让系统选择默认的协议(对于SOCK_DGRAM
,通常是 UDP)。
bind
绑定套接字
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
: 由socket()
返回的套接字文件描述符。addr
: 指向sockaddr
结构体的指针,该结构体包含了要绑定的本地地址和端口。addrlen
:addr
结构体的大小。
sendto
发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd
: 套接字文件描述符。buf
: 指向要发送数据的缓冲区。len
: 要发送的数据长度。flags
: 通常设置为 0。dest_addr
: 指向目标地址的sockaddr
结构体。addrlen
:dest_addr
结构体的大小。
recvfrom
接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sockfd
: 套接字文件描述符。buf
: 指向接收数据的缓冲区。len
: 缓冲区的大小。flags
: 通常设置为 0。src_addr
: 指向发送者地址的sockaddr
结构体。addrlen
: 指向src_addr
结构体大小的指针。
close
关闭套接字
int close(int fd);
fd
: 要关闭的文件描述符。
【testcode】
下面有一个服务是客户端给服务端发送一条msg,然后服务端将此msg输出到终端便可
服务端
udpsock_ser.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main()
{
int sockfd;
struct sockaddr_in servaddr, cliaddr;
char buffer[BUFFER_SIZE];
socklen_t len = sizeof(cliaddr);
//1.创建套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket creation failed");
exit(1);
}
//2.绑定地址
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("bind failed");
exit(1);
}
//3.接收数据
int n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
buffer[n] = '\0';
printf("Client: %s\n", buffer);
//4.关闭套接字
close(sockfd);
return 0;
}
客户端
udpsock_cli.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main()
{
int sockfd;
struct sockaddr_in servaddr;
char *hello = "Hello from client";
char buffer[BUFFER_SIZE];
//1.创建套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket creation failed");
exit(1);
}
//2.设置服务器地址
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = INADDR_ANY;
//3.发送数据
sendto(sockfd, (const char *)hello, strlen(hello), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));
printf("Hello message sent.\n");
//4.关闭套接字
close(sockfd);
return 0;
}
运行结果
数据报套接字是一种基于UDP协议的无连接通信方式,适用于实时性高、数据量小且允许部分丢包的场景。通过创建套接字、绑定地址、发送和接收数据等步骤,可以轻松实现UDP通信
【总结】
数据流套接字(Stream Socket)和数据报套接字(Datagram Socket)是计算机网络编程中两种重要的套接字类型,它们分别基于TCP协议和UDP协议实现,适用于不同的场景。以下将从定义、特点、适用场景和优缺点等方面详细对比二者的区别。
一、定义与基础概念
1. 数据流套接字(Stream Socket)
-
定义:数据流套接字是基于TCP(传输控制协议)的套接字类型,提供面向连接的、可靠的数据传输服务。
-
通信方式:需要先建立连接,然后通过连接发送和接收数据,通信结束后关闭连接。
-
协议基础:TCP协议。
2. 数据报套接字(Datagram Socket)
-
定义:数据报套接字是基于UDP(用户数据报协议)的套接字类型,提供无连接的、不可靠的数据传输服务。
-
通信方式:无需建立连接,直接发送和接收独立的数据包。
-
协议基础:UDP协议。
二、核心特点对比
特点 | 数据流套接字(TCP) | 数据报套接字(UDP) |
---|---|---|
连接性 | 面向连接,需要建立连接 | 无连接,直接发送数据包 |
可靠性 | 可靠传输,保证数据无差错、无重复、有序到达 | 不可靠传输,数据包可能丢失、重复或无序到达 |
数据传输方式 | 字节流,无记录边界 | 独立的数据包,有记录边界 |
传输效率 | 建立连接需要额外开销,传输效率较低 | 无需建立连接,传输效率高 |
流量控制 | 内置流量控制,避免拥塞 | 无流量控制,可能发生拥塞 |
适用场景 | 大数据量传输、对可靠性要求高的场景 | 实时性要求高、数据量小的场景 |
三、适用场景
1. 数据流套接字(TCP)
-
文件传输:如FTP协议,需要保证数据的完整性和顺序。
-
网页浏览:HTTP/HTTPS协议,需要可靠地传输网页内容。
-
邮件传输:如SMTP协议,确保邮件内容的完整性和可靠性。
-
远程登录:如SSH协议,需要稳定、安全的连接。
2. 数据报套接字(UDP)
-
实时通信:如视频会议、在线游戏,对实时性要求高。
-
音频/视频流:如流媒体传输,少量丢包不影响整体效果。
-
DNS查询:数据量小,查询速度快。
-
网络管理:如SNMP协议,用于网络设备监控。
四、优缺点分析
1. 数据流套接字(TCP)
- 优点:
- 数据传输可靠,适合对数据完整性要求高的场景。
- 内置流量控制和错误重传机制,避免数据丢失。
- 缺点:
- 建立连接需要额外开销,传输效率较低。
- 复杂性较高,不适合实时性要求高的场景。
2. 数据报套接字(UDP)
- 优点:
- 无需建立连接,传输效率高。
- 头部较小,适合传输少量数据。
- 实时性强,适合对延迟敏感的应用。
- 缺点:
- 数据传输不可靠,可能丢失或无序到达。
- 无流量控制,容易发生拥塞。
五、总结
- 数据流套接字基于TCP协议,提供面向连接、可靠的数据传输服务,适用于对数据完整性和顺序要求高的场景。
- 数据报套接字基于UDP协议,提供无连接、不可靠的数据传输服务,适用于实时性要求高、数据量小的场景。