Bootstrap

嵌入式知识点总结 网络编程 专题提升(三)-socket 套接字

针对于嵌入式软件杂乱的知识点总结起来,提供给读者学习复习对下述内容的强化。

目录

1.请问你有没有基于做过socket的开发?具体网络层的操作该怎么做?

2.请你来说一下socket编程中服务器端和客户端主要用到哪些函数?

3.请你讲述一下Socket编程的send() recv() accept() socket() 函数? 


1.请问你有没有基于做过socket的开发?具体网络层的操作该怎么做?

在基于Socket的开发中,虽然我们通常工作在传输层(TCP/UDP),但是通过Socket的某些高级操作,我们可以处理一些网络层的任务,如IP路由、原始Socket操作等。开发过程中,主要关注如何设置Socket、管理连接、发送和接收数据等操作,而网络层的一些细节(如路由、IP管理)通常由操作系统的网络栈处理。

1.1.TCP协议操作流程

1.1.1.服务端

创建Socket

int server_fd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET=IPv4, SOCK_STREAM=TCP

绑定IP和端口

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡
addr.sin_port = htons(8080);       // 端口字节序转换
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));

监听连接

listen(server_fd, 5); // 5=等待队列长度

接受客户端连接

struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);

收发数据

char buffer[1024];
recv(client_fd, buffer, sizeof(buffer), 0); // 接收
send(client_fd, "Hello", 5, 0);             // 发送

注意处理recv()返回0(客户端断开)或-1(错误)。

关闭连接

close(client_fd);  // 关闭客户端连接
close(server_fd);  // 关闭服务端Socket

1.1.2.客户端

创建Socket并连接

int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // IP转换
connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));

1.2.UDP协议操作流程

1.2.1.服务端

创建Socket并绑定

int sock = socket(AF_INET, SOCK_DGRAM, 0); // SOCK_DGRAM=UDP
bind(sock, (struct sockaddr*)&addr, sizeof(addr));

收发数据

struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_addr, &client_len);
sendto(sock, "Response", 8, 0, (struct sockaddr*)&client_addr, client_len);

基础回显服务

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(8080),
        .sin_addr.s_addr = INADDR_ANY
    };
    bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(server_fd, 5);

    while (1) {
        int client_fd = accept(server_fd, NULL, NULL);
        char buffer[1024];
        int len = recv(client_fd, buffer, sizeof(buffer), 0);
        send(client_fd, buffer, len, 0);
        close(client_fd);
    }
    return 0;
}

2.请你来说一下socket编程中服务器端和客户端主要用到哪些函数?

基于TCP的socket


1.服务器端程序
(1)、创建一个socket,用函数socket()
(2)、绑定IP地址、端口等信息到socket上,用函数bind()(3)、设置允许的最大连接数,用函数listen()
(4)、接收客户端上来的连接,用函数accept()
(5)、收发数据,用函数send()和recv(),或者read()和write()
(6)、关闭网络连接
2.客户端程序
(1)、创建一个socket,用函数socket()
(2)、设置要连接的对方的IP地址和端口等属性
(3)、连接服务器,用函数connect()
(4)、收发数据,用函数send()和recv(),或read()和write()
(5)、关闭网络连接

基于UDP的socket


1.服务器端流程
(1)、建立套接字文件描述符,使用函数socket(),生成套接字文件描述符。
(2)、设置服务器地址和侦听端口,初始化要绑定的网络地址结构。
(3)、绑定侦听端口,使用bind()函数,将套接字文件描述符和一个地址类型变量进行绑定。(4)、接收客户端的数据,使用recvfrom()函数接收客户端的网络数据(5)、向客户端发送数据,使用sendto()函数向服务器主机发送数据。(6)、关闭套接字,使用close()函数释放资源。UDP协议的客户端流程
2.客户端流程
(1)、建立套接字文件描述符,socket()。
(2)、设置服务器地址和端口,struct sockaddr。
(3)、向服务器发送数据,sendto()。
(4)、接收服务器的数据,recvfrom()。
(5)、关闭套接字,close()。

3.请你讲述一下Socket编程的send() recv() accept() socket() 函数? 

Socket编程中的 socket()accept()send()recv() 是核心函数,分别用于创建套接字、接受连接、发送和接收数据。

socket():创建一个新的Socket对象,用于网络通信。

bind():将套接字与地址(IP和端口)绑定,通常用于服务器端。

listen():将套接字设置为监听状态,等待客户端连接请求。

accept():接收客户端的连接请求,并返回一个新的Socket对象与客户端进行通信。

send():通过已连接的Socket发送数据。

recv():从已连接的Socket接收数据。

1. socket() 函数

socket()是创建一个新的Socket的函数,它用于在客户端或服务器端创建一个用于网络通信的端点。可以指定通信的协议族、套接字类型以及协议类型。

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

family:指定套接字的协议族,常见的有:

AF_INET:表示IPv4协议。

AF_INET6:表示IPv6协议。

AF_UNIX:表示Unix域套接字,用于本地进程间通信。

type:指定套接字类型,常见的有:

SOCK_STREAM:表示TCP连接(面向连接的流协议)。

SOCK_DGRAM:表示UDP连接(无连接的报文传输协议)。

SOCK_RAW:原始套接字,用于接触更底层的协议(例如ICMP、ARP等)。

proto:指定协议类型,通常为0,表示根据familytype自动选择合适的协议。

fileno:如果已经有一个文件描述符(FD),可以将其作为参数传递,通常不需要设置。

import socket

# 创建一个TCP套接字,IPv4协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

2. bind() 函数

bind()将套接字与本地地址(IP地址和端口号)绑定在一起,使得服务器端能够监听特定端口的连接请求。bind()通常在服务器端使用。

socket.bind(address)

address:是一个元组,包含服务器的IP地址和端口号。通常使用('0.0.0.0', port)来让服务器绑定到所有可用的网络接口,或使用('127.0.0.1', port)来仅绑定到本地接口。

# 绑定到所有IP地址和端口8080
server_socket.bind(('0.0.0.0', 8080))

3. listen() 函数

listen()用于将套接字设置为监听状态,使得服务器可以接受连接请求。它还指定了等待队列的最大长度,表示可以挂起的连接请求的数量。

socket.listen(backlog)

backlog:指定等待连接队列的大小,即最大能挂起的未完成连接请求数量。这个数字不能太小,否则可能会导致客户端连接失败。

# 监听最多5个连接请求
server_socket.listen(5)

4. accept() 函数

accept()用于服务器端等待客户端的连接请求。当客户端发起连接时,服务器端会通过accept()接受该连接。accept()返回一个新的Socket对象和客户端的地址信息。

client_socket, client_address = server_socket.accept()

client_socket:返回一个新的Socket对象,用于与客户端进行数据交换。

client_address:返回客户端的IP地址和端口号,格式是(IP, port)

# 接受客户端的连接请求
client_socket, client_address = server_socket.accept()
print(f"Connection from {client_address}")

5. send() 函数

send()用于向连接的套接字发送数据。对于TCP协议,数据将按照字节流形式发送到网络中。如果成功发送,返回实际发送的字节数;如果发送失败,返回-1。

socket.send(bytes)

bytes:要发送的数据,必须是字节类型(bytes)。如果是字符串类型,需先编码为字节格式。

# 向客户端发送数据
client_socket.send(b"Hello, client!")

6. recv() 函数

recv()用于接收从连接的套接字中传输的数据。它会阻塞直到收到数据或者连接关闭。通常在客户端和服务器之间通信时使用。

socket.recv(bufsize, flags=0)

bufsize:指定一次接收的最大字节数,通常设置为1024、2048等,表示接收的缓冲区大小。

flags:接收标志,通常设为0。可以用于设置特定的标志,比如控制接收的行为。

# 从客户端接收数据
data = client_socket.recv(1024)
print(f"Received data: {data.decode()}")
;