目录
网络编程:
TCP服务端和TCP客户端的创建过程区别
1、套接字(Socket)、IP地址和端口号
2、网络编程相关API
(1)创建套接字:socket()
(2)绑定地址和端口:bind()
(3)监听连接请求:linten()
(4)客户端发送、服务端接受连接请求:connect()和accept()
1)客户端发送连接请求:
2)服务端接受连接请求:
(5)通信
1)发送数据
send()
sendto()
sendmsg()
2)接收数据
recv()
recvfrom()
recvmsg()
在Linux中,TCP服务端与客户端的通信流程主要包括以下几个步骤:
服务器端首先调用socket()函数创建套接字,然后调用bind()函数绑定本地地址和端口号;
调用listen()函数开始监听指定端口上的连接请求;
客户端同样调用socket()函数创建套接字,然后调用connect()函数向服务器发起连接请求;
服务器端调用accept()函数接受客户端的连接请求,此时双方建立连接;
建立连接后,服务器和客户端都可以通过send()和recv()函数进行数据的收发;
数据传输完毕后,双方可以通过close()函数关闭套接字断开连接。
1. 创建套接字:
- 在服务端,首先需要调用socket()函数创建一个套接字,并且通常会选择流式套接字(SOCK_STREAM),因为TCP是一种面向连接的可靠传输协议。
- 在客户端,也需要调用socket()函数创建一个套接字,同样可以选择流式套接字。
2. 绑定地址和端口:
- 在服务端,接下来需要调用bind()函数将套接字与本地地址和端口关联起来,这样其他客户端就能找到这个服务端。通常会选择固定的端口号,并绑定到一个特定的IP地址,也可以选择所有可用的IP地址(INADDR_ANY)。
- 在客户端,不需要执行bind()函数,因为它并不需要等待其他进程来连接它,而是主动去连接服务端。
3. 监听连接请求:
- 在服务端,完成绑定后,就需要调用listen()函数进入监听状态,等待客户端的连接请求。在这个阶段,服务端并未真正建立连接,只是准备好接收连接请求。
- 在客户端,不需要执行listen()函数,而是直接调用connect()函数发起连接请求。
4. 接受连接请求:
- 在服务端,当收到客户端的连接请求时,会调用accept()函数接受连接。accept()函数会返回一个新的套接字描述符,用于与客户端之间的通信。
- 在客户端,一旦发起连接请求,就会等待服务端的响应。如果连接成功建立,那么connect()函数就会返回0。
5. 通信和关闭套接字:
- 当连接建立完成后,无论是在服务端还是客户端,都可以通过read()和write()函数来进行数据的发送和接收。
- 当通信结束或者出现异常时,可以调用close()函数来关闭套接字,释放资源。
套接字(Socket)是计算机网络中用于实现不同主机间应用程序进程间双向通信的一个软件实体。它提供了一种方法,使得应用程序可以使用网络协议进行数据的交换。
套接字通常被用来在网络中的两台机器之间建立连接,并允许它们之间传输数据。
套接字包括两个主要部分:一个是本地地址和端口号组成的标识符,另一个是根据网络层协议定义的操作方式。
例如,在互联网上浏览网页时,浏览器会与远程Web服务器建立一个套接字连接,以便从Web服务器获取网页内容。
在一个网络中,由IP地址可以唯一确定一台主机,而端口号则标识了该主机上运行的应用程序进程。
Linux系统中用于创建一个新套接字的函数,它是任何套接口网络编程中第一个使用的函数。
`socket()`函数的原型如下:
#include
int socket(int domain, int type, int protocol);
其中,
例如`AF_INET`代表IPv4,`AF_INET6`代表IPv6,`AF_UNIX`代表Unix域套接字等;
如流式套接字`SOCK_STREAM`、数据报套接字`SOCK_DGRAM`等;
如` IPPROTO_TCP`代表TCP协议,`IPPROTO_UDP`代表UDP协议等。
在成功创建套接字后,`socket()`函数将返回一个新的套接字描述符,失败则返回-1并设置`errno`变量为相应的错误代码。
Linux系统中的一个网络编程函数,它的作用是将一个已创建的套接字与其要使用的本地地址和端口关联起来。
`bind()`函数的原型如下:
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
其中,
如果成功,则`bind()`函数返回0,否则返回-1并设置`errno`变量为相应的错误代码。
#include
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
其中,
sa_family字段是指定套接字地址族的16位整数,如AF_INET代表IPv4,AF_INET6代表IPv6,AF_UNIX代表Unix域套接字等;
sa_data字段是一个包含套接字地址具体信息的数组,数组的具体内容取决于sa_family字段的值。 在实际使用中,由于struct sockaddr只是一个通用的套接字地址结构,因此通常不会直接使用它来存储套接字地址,而是使用它的子类结构如struct sockaddr_in和struct sockaddr_un等。
需要注意的是,只有未连接的套接字才能进行绑定操作。在绑定之后,套接字便不能再与其它地址和端口相关联。
Linux系统中的一个网络编程函数,它的作用是让某个已经绑定了的套接字处于被动等待连接的状态,即准备接收连接请求。
`listen()`函数的原型如下:
#include
int listen(int sockfd, int backlog);
其中,
如果成功,则`listen()`函数返回0,否则返回-1并设置`errno`变量为相应的错误代码。
需要注意的是,在调用`listen()`函数前,必须先调用`bind()`函数将套接字与本地地址和端口关联起来,并且只能对尚未连接的套接字执行此操作。
connect()函数是Linux系统中的一个网络编程函数,它的作用是从客户端的角度出发建立一个连接到服务端的过程。
`connect()`函数的原型如下:
#include
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
其中,
如果成功,则`connect()`函数返回0,否则返回-1并设置`errno`变量为相应的错误代码。
需要注意的是,只有未连接的套接字才能进行连接操作。在连接成功后,套接字便会与指定的服务端地址和端口相关联。
accept()函数是Linux系统中的一个网络编程函数,它的作用是从服务端的角度出发,接受来自客户端的连接请求,并生成一个新的套接字用于与客户端通信。
`accept()`函数的原型如下:
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
其中,
如果成功,则`accept()`函数返回一个新的套接字描述符,该套接字用于与新的客户端进行通信。否则返回-1并设置`errno`变量为相应的错误代码。
需要注意的是,只有监听状态的套接字才能调用accept()函数。当有新的连接请求到达时,accept()函数会阻塞,直到有新的连接请求为止。
`send()`函数是Linux系统中的一个网络编程函数,它的作用是发送数据到套接字连接的另一方。
`send()`函数的原型如下:
#include
ssize_t send(int sockfd, const void *msg, size_t len, int flags);
其中,
如果成功,则`send()`函数返回实际发送的数据长度,否则返回-1并设置`errno`变量为相应的错误代码。
需要注意的是,send()函数并不会立即发送全部数据,而是尽可能多地发送数据,并将剩余数据留在内核缓冲区中。如果想一次性发送所有数据,可以使用`MSG_DONTWAIT`标志。
`sendto()`函数是Linux系统中的一个网络编程函数,它的作用是发送数据到指定的套接字地址和端口。
`sendto()`函数的原型如下:
#include
ssize_t sendto(int sockfd, const void *msg, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
其中,
如果成功,则`sendto()`函数返回实际发送的数据长度,否则返回-1并设置`errno`变量为相应的错误代码。
`sendto()`函数可用于无连接的套接字,如UDP。对于TCP来说,如果已知目的地的地址和端口,也可以使用此函数发送数据。
`sendmsg()`函数是Linux系统中的一个高级网络编程函数,它的作用是发送复杂的带选项的消息。
`sendmsg()`函数的原型如下:
#include
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
其中,
如果成功,则`sendmsg()`函数返回实际发送的数据长度,否则返回-1并设置`errno`变量为相应的错误代码。
`sendmsg()`函数相比于`send()`函数更为灵活,它可以设置消息头的多种属性,如标记是否需要响应、路由选择等。它也支持多部分消息的发送,如SCM_RIGHTS等。sendmsg()
函数在大多数情况下使用较少,因为它的使用较为复杂,并且大部分应用场景都有更简单的替代方案。但有时为了实现特定的需求,如进行多部分消息的传输或传递文件描述符等,可能需要用到sendmsg()
函数。
需要注意的是,不是所有的平台都支持`sendmsg()`函数,因此在使用时应确保平台支持该函数。
`recv()`函数是Linux系统中的一个网络编程函数,它的作用是从套接字接收数据。
`recv()`函数的原型如下:
#include
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
其中,
如果成功,则`recv()`函数返回实际接收的数据长度,否则返回-1并设置`errno`变量为相应的错误代码。
需要注意的是,`recv()`函数不保证能一次读取所有数据,因此需要多次调用才能读取完整的数据。另外,如果设置了非阻塞标志,也可能只读取部分数据。
`recvfrom()`函数是Linux系统中的一个网络编程函数,它的作用是从指定的套接字接收数据。
`recvfrom()`函数的原型如下:
#include
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
其中,
如果成功,则`recvfrom()`函数返回实际接收的数据长度,否则返回-1并设置`errno`变量为相应的错误代码。
`recvfrom()`函数通常用于无连接的套接字,如UDP,它可以获取发送者的地址和端口号。对于TCP来说,如果没有特殊需求,可以直接使用`recv()`函数接收数据。
`recvmsg()`函数是Linux系统中的一个高级网络编程函数,它的作用是从指定套接字接收复杂的带选项的消息。
`recvmsg()`函数的原型如下:
#include
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
其中,
`recvmsg()`函数相比其他接收函数更加复杂,但提供了更多的控制选项,如接收多个消息段、控制数据来源等。在大多数情况下,可以使用`recv()`或`recvfrom()`代替它。
close()
`close()`函数是Linux系统中的一个文件操作函数,它不仅可以关闭普通文件,也可以用于关闭套接字。对于TCP/IP连接,可以调用close()
函数来关闭套接字,以释放占用的相关资源。
`close()`函数的原型如下:
#include
int close(int fd);
其中,
如果成功,则`close()`函数返回0,否则返回-1并设置`errno`变量为相应的错误代码。
需要注意的是,当套接字不再使用时,应该调用`close()`函数来释放资源。否则可能导致资源泄漏等问题。