socket 网络编程是一种重要的技能,能够帮助我们开发高性能、高可用的网络应用程序。
那么,如何从零开始学习 socket 网络编程呢?
首先,了解 socket 网络编程的基本概念。 socket 是一种网络通信的抽象,它允许程序通过网络进行通信。在 Linux 中, socket 是通过系统调用来实现的。
其次,了解 socket 网络编程的基本流程。 socket 网络编程的基本流程包括创建 socket、绑定地址、监听连接、接受连接和进行数据通信。
接着,学习 socket 网络编程的基本函数。 socket 网络编程的基本函数包括 socket、bind、listen、accept 和 connect。
最后,通过实战练习来巩固所学知识。可以尝试编写一个简单的客户端/服务器程序来练习 socket 网络编程的基本流程和函数。
练习多了之后,你就能熟练地使用 socket 网络编程的基本函数,并且能够轻松地开发出高性能、高可用的网络应用程序。
IP地址: IP地址是在网络上唯一标识计算机的地址,分为 IPv4 和 IPv6 两种。
TCP/UDP协议: TCP(传输控制协议)和 UDP(用户数据报协议)是两种不同的网络传输协议。 TCP
提供可靠的数据传输,保证数据的完整性和顺序,而 UDP 提供不可靠的数据传输,适用于对实时性要求较高的应用。
端口: 端口是在计算机上标识不同程序的数字,常见的有 HTTP 端口 80,FTP 端口 21 等。
DNS: 域名系统(DNS)是将域名映射到 IP 地址的一种服务。例如 www.google.com 映射到 IP 地址
216.58.194.174 。
理解这些基础知识将有助于更好地理解 socket 编程中的概念和函数。
学习 socket 编程基础知识是学习 Linux socket 编程的重要一步。下面是一些重要的 socket 编程基础知识:
套接字: 套接字是网络编程中的基本概念,是一种抽象的接口,用于在计算机之间传输数据。
套接字类型: 套接字类型可分为流套接字和数据报套接字,分别对应 TCP 和 UDP 协议。
通信协议: 通信协议是网络通信中使用的规则和标准,例如 TCP 和 UDP。
套接字函数: 套接字函数是网络编程中使用的基本函数,例如
socket()、bind()、listen()、accept()、connect()、send()和recv()等。
Socket网络编程技术
Socket 是一种网络编程技术,它提供了一种标准的 API (应用程序编程接口)
来编写网络应用程序,这些应用程序可以在不同的操作系统之间运行。
Socket 是对网络通信进行抽象,它提供了一组函数和数据结构,用于编写网络应用程序。它提供了一组标准的、可移植的接口,使网络编程变得简单。
常见的 socket 类型为流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM),分别对应 TCP 和 UDP 协议。
通常,一个 socket应用程序由一个服务器进程和一个或多个客户端进程组成。服务器进程负责监听来自客户端进程的连接请求并处理它们,而客户端进程则负责向服务器进程发送连接请求并处理服务器端的响应。
不仅仅只有 socket,网络编程还有很多其他技术和工具可用。
例如:
HTTP
,它是互联网上应用最为广泛的协议。
FTP
,它是文件传输协议。
SMTP
,它是电子邮件传输协议。
SSH
,它是用于远程登录和数据传输的安全协议。
SSL/TLS
,它是用于网络安全的加密协议。
SOAP
,它是一种用于基于 HTTP 的 Web 服务的协议。
REST
,它是一种用于构建 Web 服务的架构风格。
除了这些协议,还有很多其他的工具和技术可用于网络编程,例如数据库,编程语言和框架,安全工具等等。
TCP和UDP是两种不同的协议,它们之间有一些显著的区别。
TCP(传输控制协议)是一种面向连接的协议,它在发送数据之前需要建立一个连接。它保证了数据的可靠传输,可以重新发送丢失的数据。TCP协议会将数据分成小块并编号,然后依次发送,接收端会确认接收到的数据,如果没有收到确认,发送端会重发该数据块。TCP协议在发送大量数据时会消耗较多的带宽,但是它可以保证数据的可靠性。
UDP(用户数据报协议)是一种无连接协议,它不需要建立连接就可以发送数据。UDP协议不保证数据的可靠传输,数据可能会丢失或重复,并且没有重新发送丢失的数据的机制。UDP协议不会浪费带宽,因为它不会等待确认,但是它可能会导致数据传输不可靠。
总之,TCP协议更适合需要可靠数据传输的应用,而UDP协议则更适合需要高速数据传输的应用。
TCP所需API
TCP socket 编程是在 Linux 下编写网络应用程序的一种常用方法。主要的 API 包括:
socket() - 创建套接字
bind() - 绑定套接字
listen() - 监听连接请求
accept() - 接受客户端连接
connect() - 连接服务器
send() / sendto() - 发送数据
recv() / recvfrom() - 接收数据
close() - 关闭套接字
详细细节可以参考linux相关文档和教程。
UDP所需API
UDP socket 编程与 TCP socket 类似,但有一些不同之处。主要的 API 包括:
socket() - 创建套接字
bind() - 绑定套接字
connect() - 连接服务器 (可选)
sendto() - 发送数据
recvfrom() - 接收数据
close() - 关闭套接字
UDP 不需要 listen() 和 accept() 这两个 API ,因为它不建立连接。
另外,UDP 的 send() 和 recv() 函数分别对应 sendto() 和 recvfrom()。
详细细节可以参考linux相关文档和教程。
int socket(int domain, int type, int protocol);
int sockfd;
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
创建了一个AF_INET(IPv4),类型为SOCK_STREAM(TCP)的套接字。
请注意,这只是一个简单的实例。实际编写网络应用程序时,还需要其他步骤,如绑定地址和端口,连接服务器等。
bind() 函数绑定一个 socket 到一个特定的 IP 地址和端口。函数原型为:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
例如,下面的代码绑定了一个 socket 到本地 IP 地址和端口 8000:
struct sockaddr_in server_addr;
// 初始化sockaddr_in结构体
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 将套接字地址信息绑定到sockfd上
if (bind(sockfd, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) {
perror("绑定失败");
exit(EXIT_FAILURE);
}
上面的代码是一个创建套接字并绑定地址和端口的例子。我们首先创建了一个sockaddr_in结构体,用来存储IPv4地址,端口为8000,地址为INADDR_ANY(通配地址)的套接字地址信息。然后我们使用bind()函数将这个地址信息绑定到已经创建的sockfd上。
请注意,这只是一个简单的实例。在实际编写网络应用程序时,还需要其他步骤,如监听和接受连接等。
bind()
函数需要传递一个指向结构体的指针作为参数。其中最常用的结构体是 sockaddr_in
,它用于存储 IPV4 的地址和端口信息。这个结构体定义在
头文件中。
同时还有sockaddr_in6
用于存储IPV6地址和端口信息,定义在
头文件中。
struct sockaddr_in {
sa_family_t sin_family; /* 地址家族,通常为 AF_INET */
in_port_t sin_port; /* 端口,使用网络字节顺序 */
struct in_addr sin_addr; /* IP 地址 */
};
INADDR_ANY 是一个特殊的 IP 地址,它表示任意地址。当绑定一个 socket 时,如果将 `INADDR_ANY` 作为 IP 地址,它会监听所有可用的网络接口上的连接。
上面的代码将 socket 绑定到本地所有可用的 IP 地址和 8000 端口,这样的服务器可以接收通过所有网络接口发送到 8000 端口的请求。
需要注意的是`INADDR_ANY` 只能用于`AF_INET` 地址族中,`AF_INET6`中则使用 `in6addr_any`
它常用在服务器端程序中,它允许服务器监听所有可用的网络接口上的连接请求,而不需要指定具体的 IP 地址。
htons()和htonl()都是用来将本地字节序转换为网络字节序的函数。
htons()是将一个无符号短整型数值从主机字节顺序转换为网络字节顺序。
htonl()是将一个无符号长整型数值从主机字节顺序转换为网络字节顺序。
为什么要进行字节序转换呢,因为不同系统中,字节序是不同的,而网络传输是需要统一字节序的,所以在网络编程中需要进行字节序转换。
这两个函数的功能是相似的,只是转换的数据类型不同,htons()转换的是2个字节的数据,htonl()转换的是4个字节的数据。
listen() 函数是将一个未连接的 socket 转换为一个监听 socket。
int listen(int sockfd, int backlog);
listen() 函数在成功时返回 0,在失败时返回 -1。
举个例子,在服务器端,我们可以这样使用 listen() 函数:
if (listen(sockfd, 10) < 0) {
perror("监听失败");
exit(EXIT_FAILURE);
}
上面的代码是一个调用listen()函数的例子。我们将sockfd设置为监听状态,并设置其最大连接数为10。这样,当有客户端试图连接到服务器时,内核就会将其加入到等待队列中。
当连接数达到最大值时,内核就会拒绝新的连接请求。
注意,在调用listen()之前,我们必须先调用bind()将套接字绑定到特定的地址和端口上。
accept() 函数是用于接收客户端的连接请求的。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept() 函数在成功时返回一个新的 socket 描述符,该描述符用于与连接的客户端进行通信,在失败时返回 -1。
举个例子,在服务器端,我们可以这样使用 accept() 函数:
int client_sockfd;
struct sockaddr_in client_address;
socklen_t client_len = sizeof(client_address);
// accept是阻塞函数,它会等待客户端的连接
// 返回值为新建立连接的客户端套接字描述符
client_sockfd = accept(sockfd, (struct sockaddr *)&client_address, &client_len);
if (client_sockfd < 0) {
perror("连接失败");
exit(EXIT_FAILURE);
}
上面的代码是调用accept()函数的一个例子。它会在客户端连接到服务器后返回一个新的套接字描述符,用于与连接的客户端通信。该套接字描述符与原来的套接字描述符(sockfd)不同,因为它是专门用于与连接的客户端通信的。
参数 (struct sockaddr *)&client_address 和 client_len 用于获取客户端的地址信息。
注意,在调用accept()之前,我们必须先调用listen()将套接字设置为监听状态。
connect() 函数是用于客户端向服务器发起连接请求的。
该函数的原型如下:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect() 函数在成功时返回 0,在失败时返回 -1。
举个例子,在客户端,我们可以这样使用 connect() 函数:
struct sockaddr_in server_address;
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_port = htons(SERVER_PORT);
server_address.sin_addr.s_addr = inet_addr(SERVER_IP);
// connect是阻塞函数,它会等待服务器的响应
// 返回值为0表示连接成功,-1表示连接失败
if (connect(sockfd, (struct sockaddr *) &server_address, sizeof(server_address)) < 0) {
perror("连接失败");
exit(EXIT_FAILURE);
}
上面的代码是调用connect()函数的一个例子。它会尝试建立到服务器的连接。该函数是阻塞函数,它会等待服务器的响应。如果连接成功,connect()函数会返回0,否则会返回-1并设置errno。
参数 (struct sockaddr *)&server_address 表示服务器的地址信息,sizeof(server_address) 表示地址信息的大小。
在调用connect()之前,我们需要先调用socket()函数创建套接字。
send()
函数是用于已连接套接字(TCP)发送数据。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
在一般情况下,可以将 flags 参数设置为0,表示不使用任何特殊选项。
返回值表示成功传输的数据字节数。如果返回-1,表示发生错误。
代码示例:
int sockfd;
char buffer[BUFFER_SIZE];
// 这里假设已经调用connect()成功建立了连接
sprintf(buffer, "Hello, World!");
// send是阻塞函数,它会等待服务器的响应
// 返回值为发送的字节数,-1表示发送失败
int n = send(sockfd, buffer, strlen(buffer), 0);
if (n < 0) {
perror("发送失败");
} else {
printf("已发送%d字节\n", n);
}
上面的代码是调用send()函数的一个例子。它会向服务器发送数据。该函数是阻塞函数,它会等待服务器的响应。如果发送成功,send()函数会返回发送的字节数,否则会返回-1并设置errno。
参数 buffer 是要发送的数据,strlen(buffer) 是要发送的数据的大小。
在调用send()之前,我们需要先调用connect()函数建立连接。
函数用于从网络套接字接收数据
recv(int sockfd, void *buf, size_t len, int flags);
recv()函数的flags参数是一个可选参数,用来设置接收数据的行为。常用的选项有:
在调用 recv() 函数之后,如果接收到了数据,则返回实际接收到的字节数。如果连接已关闭或发生错误,则返回-1.
int sockfd;
char buffer[BUFFER_SIZE];
// 这里假设已经调用connect()成功建立了连接
// recv是阻塞函数,它会等待服务器发送数据
// 返回值为接收的字节数,0表示连接已关闭,-1表示接收失败
int n = recv(sockfd, buffer, BUFFER_SIZE, 0);
if (n == 0) {
printf("连接已关闭\n");
} else if (n < 0) {
perror("接收失败");
} else {
printf("已接收%d字节\n", n);
printf("接收到的数据: %s\n", buffer);
}
上面的代码是调用recv()函数的一个例子。它会从服务器接收数据。该函数是阻塞函数,它会等待服务器发送数据。如果接收成功,recv()函数会返回接收的字节数,返回0表示连接已关闭,返回-1表示接收失败。
参数 buffer 是接收到的数据, BUFFER_SIZE 是缓冲区大小。
在调用recv()之前,我们需要先调用connect()函数建立连接。
函数用于关闭套接字的连接。
int shutdown(int sockfd, int how);
调用 shutdown() 函数会立即关闭套接字的连接,但是套接字的文件描述符仍然可以用于其他操作。如果调用成功,则返回0,如果出现错误,则返回-1。
下面是一个 shutdown() 的示例代码:
#include
#include
#include
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 创建套接字
// ... 其他代码 ...
int ret = shutdown(sockfd, SHUT_WR);
// 关闭写,不能再发送数据
if (ret == -1) {
perror("shutdown error");
// 输出错误信息
} else {
printf("shutdown success\n");
// 输出成功信息
}
return 0;
}
在上面的代码中,首先通过 socket() 函数创建了一个套接字,然后调用 shutdown() 函数关闭了写,不能再发送数据。如果调用成功,则会输出 “shutdown success”,否则输出 “shutdown error” 和具体的错误信息。
请注意,这仅仅是一个示例代码,实际中还需要进行其他的操作,如设置套接字地址等。
close函数用于关闭网络连接,参数 sockfd 是要关闭的套接字的文件描述符。
int close(int sockfd);
如果关闭成功,close()函数会返回0,否则返回-1。
实例:
int sockfd;
// 关闭套接字
int ret = close(sockfd);
if (ret == 0) {
printf("套接字已关闭\n");
} else {
perror("关闭套接字失败");
}
上面的代码是调用close()函数的一个例子。这个函数用来关闭一个已经打开的套接字。在关闭之后,套接字将不能再使用。
参数 sockfd 是要关闭的套接字的文件描述符。
如果关闭成功,close()函数会返回0,否则返回-1。
对于服务器与客户端通信的实例,首先需要创建服务器端程序和客户端程序。
在服务器端程序中,首先需要进行 socket()、bind()、listen() 操作来创建一个 socket 并绑定端口,并监听连接请求。然后使用 accept() 接收客户端的连接请求,进行通信。在通信过程中可以使用 send() 和 recv() 进行数据传输。最后使用 close() 关闭 socket。
详细的代码实现如下:
#include
#include
#include
#include
#include
#include
#define PORT 8888
#define MAX_LINE 1024
int main() {
// 创建一个 socket
int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 绑定 socket 到一个 IP 地址和端口
struct sockaddr_in server_address;
// 将套接字地址结构清零
bzero((char *) &server_address, sizeof(server_address));
//memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 可以绑定到所有网络接口
server_address.sin_port = htons(PORT);
bind(server_sockfd, (struct sockaddr *) &server_address, sizeof(server_address));
// 监听客户端连接请求
listen(server_sockfd, 5);
// 接受客户端连接
struct sockaddr_in client_address;
socklen_t client_len = sizeof(client_address);
int client_sockfd = accept(server_sockfd, (struct sockaddr *) &client_address, &client_len);
// 循环处理客户端发来的消息
while (1) {
char buffer[MAX_LINE];
int len = recv(client_sockfd, buffer, sizeof(buffer), 0);
if (len == 0) {
// 客户端关闭连接
break;
}
buffer[len] = '\0';
printf("客户端发来的消息: %s\n", buffer);
// 向客户端发送消息
char *send_msg = "已收到消息";
send(client_sockfd, send_msg, strlen(send_msg), 0);
}
// 关闭 socket
close(client_sockfd);
close(server_sockfd);
return 0;
}
在客户端程序中,首先需要进行 socket() 操作来创建一个 socket。然后使用 connect() 函数连接服务器端。在通信过程中可以使用 send() 和 recv() 进行数据传输。最后使用 close() 关闭 socket。
#include
#include
#include
#include
#include
#include
#define PORT 8888
#define MAX_LINE 1024
int main() {
// 创建一个 socket
int client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 向服务器端发起连接
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &server_address.sin_addr); // 连接本地服务器
server_address.sin_port = htons(PORT);
connect(client_sockfd, (struct sockaddr *) &server_address, sizeof(server_address));
// 循环向服务器端发送消息
while (1) {
char *send_msg = "你好,服务器";
send(client_sockfd, send_msg, strlen(send_msg), 0);
// 接收服务器端的消息
char buffer[MAX_LINE];
int len = recv(client_sockfd, buffer, sizeof(buffer), 0);
buffer[len] = '\0';
printf("服务器端发来的消息: %s\n", buffer);
//延时发送
sleep(5);
}
// 关闭 socket
close(client_sockfd);
return 0;
}
这是一个简单的Linux socket服务器与客户端之间的对话,上面的代码中的IP地址和端口号应该根据实际情况进行修改。
上面给出的Linux socket服务器与客户端之间的对话程序是一个简单的示例,它可以让服务器端和客户端进行交互。
在运行服务器端程序后,它会等待客户端连接,一旦客户端连接上来,服务器端就会接收客户端发来的消息并打印到屏幕上,然后向客户端发送一条"已收到消息"的消息。客户端程序每隔一段时间向服务器发送"
inet_pton()是一个将字符串类型的IP地址转换为二进制类型的IP地址的函数。
int inet_pton(int af, const char *src, void *dst);
其中:
inet_pton()函数返回值:
如在上面的例子中,客户端连接本地服务器,inet_pton(AF_INET, “127.0.0.1”, &server_address.sin_addr); 将字符串类型的IP地址"127.0.0.1"转换成二进制类型的IP地址并赋值给server_address结构体中的sin_addr字段。
inet_ntop()函数是将二进制类型的IP地址转换为字符串类型的IP地址。
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);
其中:
inet_ntop()函数返回值:
inet_ntop()函数在转换IPv4地址和IPv6地址时,输入的二进制类型的IP地址的数据类型和大小都是不同的,因此需要根据地址族的不同进行不同的转换。
这个函数常用在网络编程中,将二进制类型的IP地址转换为字符串类型的IP地址,方便进行网络通信。
使用inet_ntop()函数的好处在于它支持IPv4和IPv6地址, 而inet_ntoa()只支持IPv4地址,并且它返回的地址是静态的,不能修改,且只供读取。
inet_aton()
和inet_ntoa()
都是与inet_pton()
类似的函数,用来转换IP地址,主要区别在于输入和输出数据类型不同。
inet_aton()
函数将字符串类型的IP地址转换为二进制类型的IP地址。
int inet_aton(const char *cp, struct in_addr *inp);
其中:
inet_aton()函数返回值:
inet_ntoa()
函数将二进制类型的IP地址转换为字符串类型的IP地址。
char *inet_ntoa(struct in_addr in);
其中:
输入的二进制类型的IP地址的字符串形式。
注意:inet_ntoa()返回的字符串是静态的,不可以改变,且仅供读取,不能修改。
这两个函数在Linux系统中已经过时,建议使用inet_pton()和inet_ntop()来进行IP地址转换。
在inet_pton中,使用127.0.0.1作为地址意味着只能在本地访问该服务。这是因为127.0.0.1是回环地址,只能在本机上使用,而不能在其他设备上使用。如果要使用外网访问服务,则需要使用服务器的公网IP地址。
回环地址
回环地址是一个IP地址,在TCP/IP协议中被定义为127.0.0.1。它表示本地主机,通常用于本地测试和调试。因为它是一个特殊的地址,所以不能用于连接到互联网上的其他主机。
公网IP地址
公网IP地址,又称公网IP或公开IP,是指在互联网上可以直接访问的IP地址。公网IP地址可以被所有互联网用户访问到,它的地址范围是在公网地址段中。对于一个个人用户来说,他的电脑或手机连接到互联网时,会被分配一个公网IP地址,这样其他用户就可以通过这个IP地址来访问该用户的设备上的资源。
ifconfig
Linux中可以使用ifconfig
命令来查看相应IP地址
例如:
在Linux(我用的是Ubuntu)环境中输入,“ifconfig”
听说IP地址不要泄露,乱涂了一顿不过,图中圈红的地方就是你的ip地址。
如果对你有帮助,点个赞支持一下,持续分享学习过程。
转载请联系本人。