Socket编程基础(易懂版)

分类: 365bet滚球网站 时间: 2025-09-15 04:57:31 作者: admin 阅读: 3863
Socket编程基础(易懂版)

这篇是我在学习Socket(套接字)编程过程中的笔记整理,收集学习了一些网上的文章,但我想要用我习惯的方式(用门铃来比喻套接字)记录下学习的内容,方便日后查看。

一、Socket编程是什么?

1、概念

Socket编程是一种实现网络通信的编程技术,它允许不同主机上的应用进程之间进行数据交换。

想象一下,家门上的门铃其实是一个Socket。每当有人(数据包)按下门铃,不管他们在哪儿,只要还在地球上,你都能知道有人来访。在网络世界里,Socket就像这个门铃,它允许两个程序不管距离多远,都能相互“拜访”和交换信息。

2、Socket的类型:流式Socket(TCP)和数据报Socket(UDP)

TCP套接字:提供可靠的、面向连接的通信。(对讲机式门铃)

可靠连接:TCP套接字就像是带有对讲机的门铃。当你按下门铃,不仅能通知屋内有人来访,还能立即通过对讲机与访客对话,确认他们的身份和来意。

数据有序:使用这种门铃,你可以根据访客按门铃的顺序,一个接一个地与他们对话,不会出现混乱。

流量控制:如果一次性来的访客太多,对讲机系统会自动告诉一些访客稍等,以避免屋内变得过于拥挤。

拥塞控制:如果发现路上(网络)太拥挤,对讲机系统也会自动调整访客的到达速度,以防止过度拥堵。

错误恢复:如果对话中出现了问题(比如信号干扰),对讲机系统会尝试重新连接,确保信息准确传达。

UDP套接字:提供不可靠的、无连接的通信。(传统门铃)

无连接:UDP套接字就像是传统的门铃,只负责通知你有人来访,但不提供任何对话功能。

快速传送:因为没有对话确认的步骤,这种门铃允许访客快速按铃,适合不需要立即回复的情况。

不保证有序:由于没有对话功能,无法保证你与访客的交流顺序,可能需要你自己来维持秩序。

可能丢包:因为没有确认机制,有些按铃的信号可能因为各种原因丢失,需要你自己判断是否需要回应。

简单高效:这种门铃结构简单,使用方便,适合于不需要复杂交互的场景,如简单的信息提醒或广播通知。

TCP:适合于需要确保信息准确无误地传达的场合,比如正式的商务会议或者重要的信息交流。UDP:适合于速度要求高、可以容忍一些误差的场合,比如实时的游戏数据传输或者电视直播。 二、Socket API理解

Socket编程通常遵循以下步骤:

创建Socket——socket():创建一个socket对象。

绑定Socket——bind():一旦socket被创建,通常需要将其绑定到一个特定的网络地址和端口上,这样它就可以监听进入的连接请求。

绑定过程就像是给门铃安装一个门牌号码。这个号码包括了网络地址(IP地址)和门铃的“房间号”(端口号)。这样,当有人按门铃时,不仅知道有人来访,还能知道这个访客是想要访问家里的哪个“房间”(服务)。

监听连接(对于服务器端)——listen():服务器端的socket需要监听进入的连接请求。

当门铃安装好并有了门牌号码后,就可以开始“监听”门铃了。在网络世界里,监听就像是你站在门旁,等待门铃响起,这意味着有人在请求进入。

连接到服务器(对于客户端)——connect():客户端需要使用connect()函数来连接到服务器的socket。

对于客户端来说,连接就像是站在门外按响邻居家的门铃。当你按下门铃并等待邻居响应时,你实际上是在发送一个连接请求。

接受连接(对于服务器端)——accept():当服务器监听到连接请求时,使用accept()函数来接受连接,这将创建一个新的socket用于与客户端通信。

当服务器“听到”门铃并“查看”门牌号码后,它会决定是否开门。接受连接就像是打开门,欢迎来访的客人进入。

数据传输——send(),recv():一旦socket连接建立,就可以使用send()和recv()函数(或类似的函数,如write()和read())来发送和接收数据。

关闭Socket——close():通信完成之后,需要通过close()函数关闭socket来释放资源。

三、Socket API原型

Socket编程包含的头文件:

#include

#include

1、创建Socket —socket()

原型:

int socket(int domain, int type, int protocol);

参数:

domain: 指定socket的协议域,常用的有:

AF_INET: IPv4AF_INET6: IPv6AF_UNIX: Unix域sockettype: 指定socket的通信方式,常用的有:

SOCK_STREAM: 面向连接的流式socket,如TCPSOCK_DGRAM: 无连接的包式socket,如UDPSOCK_SEQPACKET: 面向序列包的socketprotocol: 指定协议,通常设置为0以使用默认协议。返回值:

成功: 新创建的socket的文件描述符(fd)失败: -1,并设置errno以指示错误类型2. 绑定Socket —bind()

原型:

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

参数:

sockfd: 由socket()创建的socket文件描述符。addr: 指向sockaddr结构体的指针,该结构体包含了要绑定的地址信息。addrlen: sockaddr结构体的大小。返回值:

成功: 0失败: -1,并设置errno3. 监听连接 —listen()

原型:

int listen(int sockfd, int backlog);

参数:

sockfd: 已绑定到地址的socket文件描述符。backlog: 指定内核用于存放未连接但已排队的连接请求的最大数量。返回值:

成功: 0失败: -1,并设置errno4. 接受连接 —accept()

原型:

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

参数:

sockfd: 监听socket的文件描述符。addr: 如果非NULL,接受连接后,该参数将被填充为客户端的地址信息。addrlen: 传入时,指向存放addr地址结构体长度的变量的指针;传出时,返回实际的地址长度。返回值:

成功: 一个新的socket文件描述符,用于与客户端通信。失败: -1,并设置errno5. 建立连接 —connect()

原型:

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

参数:

sockfd: 客户端socket文件描述符。addr: 指向包含服务器地址信息的sockaddr结构体的指针。addrlen: sockaddr结构体的大小。返回值:

成功: 0失败: -1,并设置errno6. 数据传输

发送数据 —send()

原型:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:

sockfd: socket文件描述符。buf: 指向要发送数据的缓冲区的指针。len: 要发送数据的长度。flags: 控制发送操作的标志位,通常为0。返回值:

成功: 发送的字节数失败: -1,并设置errno接收数据 —recv()

原型:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:

sockfd: socket文件描述符。buf: 接收数据的缓冲区。len: 缓冲区的长度。flags: 控制接收操作的标志位,通常为0。返回值:

成功: 接收的字节数失败: -1,并设置errno0: 对应的socket连接已关闭7. 关闭Socket —close()

原型:

int close(int sockfd);

参数:

sockfd: 要关闭的socket文件描述符。返回值:

成功: 0失败: -1,并设置errno8. 关闭连接方向 —shutdown()

原型:

int shutdown(int sockfd, int how);

参数:

sockfd: socket文件描述符。how: 指定如何关闭socket:

SHUT_RD: 关闭接收方向,不再读取数据。SHUT_WR: 关闭发送方向,不再发送数据。SHUT_RDWR: 关闭接收和发送方向。返回值:

成功: 0失败: -1,并设置errno四、示例

下面是一个使用TCP协议的socket服务器端的示例,主要是创建一个简单的echo服务器,该服务器接收客户端发送的数据并将其回传给客户端。

#include

#include

#include

#include

#include

#include

#include

#include

#define PORT 8080 // 服务器监听的端口号

int main() {

int server_fd, new_socket;

struct sockaddr_in server_addr, client_addr;

socklen_t client_len = sizeof(client_addr);

char buffer[1024] = {0};

// 创建socket(socket)

server_fd = socket(AF_INET, SOCK_STREAM, 0);

if (server_fd == -1) {

perror("socket creation failed");

exit(EXIT_FAILURE);

}

// 初始化服务器地址结构体(bind之前的准备)

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

server_addr.sin_family = AF_INET; // 地址族

server_addr.sin_addr.s_addr = INADDR_ANY; // 服务器IP地址(任意)

server_addr.sin_port = htons(PORT); // 服务器端口

// 将socket绑定到地址(bind)

if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {

perror("bind failed");

exit(EXIT_FAILURE);

}

// 开始监听传入连接(listen)

if (listen(server_fd, 5) < 0) {

perror("listen failed");

exit(EXIT_FAILURE);

}

printf("Server is listening on port %d...\n", PORT);

// 接受客户端连接(accept)

new_socket = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);

if (new_socket < 0) {

perror("accept failed");

exit(EXIT_FAILURE);

}

// 接收客户端发送的数据(recv)

int valread = recv(new_socket, buffer, 1024, 0);

if (valread < 0) {

perror("recv failed");

close(new_socket);

exit(EXIT_FAILURE);

}

// 发送数据回客户端(send)

send(new_socket, buffer, strlen(buffer), 0);

// 关闭新的socket(close)

close(new_socket);

// 关闭监听socket

close(server_fd);

return 0;

}

在这个例子当中,服务器首先创建了一个socket,然后将其绑定到本地的8080端口。接着,服务器进入监听状态,等待客户端的连接请求。当客户端连接时,服务器接受这个连接,创建一个新的socket用于与客户端通信。服务器接收客户端发送的消息,然后使用相同的消息进行回应。最后,服务器关闭用于通信的socket以及监听socket。

需要注意的是,这个例子是一个简化的版本,仅仅是为了展示Socket API 的使用,没有包含错误处理和多线程/异步处理的逻辑,这些在实际的服务器应用中是必需要有的。而且,服务器在接收到客户端的消息后立即关闭了连接,实际的服务器可能会维护一个持久的连接或同时处理多个连接。

相关推荐