目录
1.认识socket编程
网络通信的本质
介绍socket编程之间,我们先来看看网络通信的本质。
网络通信的本质是什么呢?我们需要明确一点,网络通信需要IP地址和端口号。IP地址在标识网络中唯一的一台主机,端口号标识主机中唯一的一个进程,IP地址加端口号就能标识网络中唯一的一台主机上唯一的一个进程。也就是说,通过IP地址和端口号,就可以找到整个网络中唯一的一个进程。所以网络通信的本质就是进程间通信。
更何况,用户在使用设备进行通信的时候,使用的是设备上的应用服务,应用服务本身就是启动的进程。
什么是socket编程?
网络通信的本质是进程间通信,是一种需要依靠IP地址和端口号的进程间通信,我们把这种基于IP地址和端口号的进程间通信叫做socket通信。socket的中文意思是插座,socket通信的模式类似于插板插座这样的模式,通信双方都必须要知道对方的IP地址和端口号就相当于把插板和插线接通,这样才能进行通信。而基于这种模式进行网络通信的编程就是socket编程。(Socket,通常也称为“套接字”)
如何进行socket编程?
进行网络编程需要依靠网络层的网络传输协议,而网络传输协议是在操作系统内部实现的,用户不能直接访问操作系统内部的代码数据,这个时候,操作系统就要提供系统调用接口供用户使用,从而进行网络编程。这些接口就是socket编程的接口。
socket编程接口是传输层供给应用层的编程接口,位于TCP/IP四层模型中的应用层与传输层之间,是应用层与传输层之间的桥梁。而传输层常用的通信协议有UDP协议和TCP协议,于是socket编程便有了基于UDP协议的编程和基于TCP协议的编程。
2.基于UDP的socket编程
当我们进行网络编程时,往往是基于CS模式(客户端服务器端模式),所以需要编写客户端程序和服务器端程序。
进行socket编程时往往需要包含这些头文件:
服务器端程序编写步骤
1.创建socket
示例代码:
socket函数介绍:
参数:
1.domain用于表明通信的类型。网络通信本质上是一种进程间通信,进程间通信有本地通信和网络通信。网络通信往往将这个参数设置为AR_INET。
2.type这个参数表明socket的类型,我们现在讲解的是基于UDP的通信,UDP是一种面向数据报的协议,所以,这个参数往往被设置成SOCK_DGRAM。
3.这个参数用于表明协议的类型。但是前面两个参数已经表明了协议的类型,所以这个参数通常设置为0。
返回值:
1.创建socket成功返回一个文件描述符,与该文件描述符所关联的文件通常用于进行网络通信中数据的收发。
2.创建socket失败返回-1,并设置全局的错误码,表明错误的原因。
2.将本地信息和网络信息进行绑定
示例代码:
bind函数介绍:
在绑定信息前,需要填充struct sockaddr_in结构,作为参数传递时,需要强转为struct sockaddr 类型。
参数:
1.sockfd表明要绑定的本地信息,也就是用于网络通信的文件。
2.addr表明自己的IP地址和端口号。这是一个结构体类型,所以我们需要填充结构体中的字段,具体可参考上面代码实例。
3.addrlen表明addr指向结构体的大小,通常使用sizeof操作符获得。
返回值:
1.成功时:如果
bind
函数成功地将套接字与指定的IP地址和端口号绑定,它会返回0
。2.失败时:如果
bind
函数调用失败,它会返回-1
,并且会设置全局变量errno
以指示具体的错误原因。
3.接收数据
服务器端程序需要先收数据,再发消息进行响应。
示例代码:
recvfrom函数介绍:
UDP是面向数据报的,文件的读写是面向字节流的,所以不能直接使用文件读写的接口。
参数:
1.sockfd 指明从哪个文件中进行读取数据。
2.buf 表明读取的数据存放的缓冲区。
3.len 表明了接收数据的最大长度,以字节为单位。
4.flags 表明了接收数据的类型,通常设置为0。
5.src_addr 是一个输出型参数,用户获取发送方的网络信息(IP地址和端口号)
6.addrlen 是一个指向
socklen_t
变量的指针,该变量在调用时应该被设置为src_addr
指向的结构体的大小;在函数返回时,它将被设置为实际存储在src_addr
中的地址的实际大小。返回值:
- 成功时,
recvfrom
返回接收到的字节数。- 如果连接已正常关闭,则返回0。
- 如果发生错误,则返回-1,并设置
errno
以指示错误类型。
4.发送数据
示例代码:
sendto函数介绍:
参数:
1.sockfd 表明通过哪个文件中发送数据。
2.buf 表明发送数据的地址。
3.len 指定了
buf
中数据的长度,即要发送的字节数。4.flags 指定了发送操作的行为,通常设置为0。
5.dest_addr 表明接收方的网络信息。
6.addrlen 是
dest_addr
指向的地址结构的大小,以字节为单位。返回值:
- 成功时,
sendto
返回发送的字节数。这可能与请求发送的字节数相同,也可能不同(例如,在某些非阻塞或错误情况下)。- 如果发生错误,则返回-1,并设置
errno
以指示错误类型。
客户端程序编写步骤
1.创建socket
这一步和服务器端的一样。客户端创建好socket之后,直接就可以进行数据的收和发了。
2.发送数据
当客户端创建socket套接字之后,直接就可以进行数据的收发了;需要明确的是,客户端需要先发送数据,然后接收数据。
示例代码:
这一步和服务器端也是一样的,参考服务器端即可。
3.接受数据
示例代码:
使用同服务器端。
3.基于TCP的socket编程
服务器端程序编写步骤
1.创建流式套接字
示例代码:
创建流式套接字使用的接口还是socket(),只不过第二个参数需要写成 SOCK_STREAM,这是因为TCP是面向字节流的协议,SOCK_STREAM表明创建流式套接字。
2.绑定本地信息和网络信息
示例代码:
使用上和udp服务器端的bind完全相同。
3.将流式套接字设置为监听状态
TCP是有连接的协议,客户端和服务器端需要建立连接才能进行通信,将流式套接字设置为监听状态的目的是用于监听连接的状态。
示例代码:
listen函数介绍:
参数:
sockfd
:是socket
函数返回的文件描述符,代表了一个打开的套接字。backlog
:指定了系统应该为相应套接字排队的最大连接个数。如果队列满了,则新的连接请求将被拒绝。这个参数的值至少为0,但具体能支持的最大值依赖于系统实现。返回值:
- 成功时,
listen
函数返回0。- 出错时,返回-1,并设置相应的errno以指示错误原因。
4.获取连接
listen函数将socket返回的套接字设置为监听套接字之后,监听套接字就会监听连接建立的状态,当有连接建立时,accept函数就会从监听套接字中获取连接,并返回一个新的文件描述符,用于建立该连接的客户端和服务器之间的通信。
服务器只有一个,但是客户端可以有很多,多个客户端和一个服务器之间要进行通信,就要求服务器中需要为每个连接建立用于通信套接字。
示例代码:
accept函数介绍:
参数:
- sockfd:服务器端套接字描述符,这是由
socket
函数创建并已经通过bind
函数绑定到特定地址的监听套接字。- addr:一个指向
sockaddr
结构体的指针,用于存储连接的对端(即客户端)的地址信息。如果调用者对此不关心,可以将此参数设置为NULL
。- addrlen:一个指向
socklen_t
类型的指针,用于指定addr
缓冲区的大小。在调用accept
之前,应该将addrlen
初始化为addr
缓冲区的大小,函数返回时,addrlen
会被设置为实际地址结构的长度。返回值:
- 成功时,
accept
函数返回一个新的文件描述符(套接字描述符),这个新的文件描述符用于与客户端进行通信。- 出错时,返回-1,并设置
errno
以指示错误原因。
5.接收消息
因为TCP是面向字节流的协议,所以我们可以使用文件读写接口 read 和 write。
示例代码:
read函数介绍:
参数:
- fd:文件描述符(file descriptor),是一个非负整数,代表了一个打开的文件、管道或套接字。它是通过之前对
open
、pipe
、socket
等系统调用的调用获得的。- buf:指向缓冲区的指针,该缓冲区用于存储从文件描述符指向的资源中读取的数据。
- count:请求读取的字节数。这是尝试从文件中读取的最大字节数。
返回值:
- 成功:返回实际读取的字节数,这个值可能小于请求读取的字节数(
count
),尤其是在到达文件末尾(EOF)或发生某些类型的非阻塞 I/O 操作时。- 失败:返回
-1
,并设置errno
以指示错误原因。
6.发送消息
代码示例:
write函数介绍:
参数:
- fd:文件描述符(file descriptor),是一个非负整数,代表了一个打开的文件、管道或套接字。
- buf:指向缓冲区的指针,该缓冲区包含了要写入文件描述符指向的资源中的数据。
- count:要写入的字节数。
返回值:
- 成功:返回实际写入的字节数。这个值可能与请求写入的字节数(
count
)相同,也可能不同,特别是在写入非阻塞文件描述符或管道时,如果没有足够的空间来保存所有请求的数据,则可能只写入部分数据。- 失败:返回
-1
,并设置errno
以指示错误原因。
客户端程序编写步骤
1.创建套接字
客户端中创建套接字使用的还是socket函数,需要注意的是,第二个参数设置为SOCK_STREAM,表明是基于TCP协议进行的通信。
2.发起连接
TCP是有连接的协议,客户端想要和服务器端进行通信时,首先要发起建立连接的请求,服务器端的监听套接字监听到请求建立连接成功之后,accept函数就会从监听套接字中获取连接,并分配一个文件描述符,该文件描述符所关联的文件用于建立连接的双方进行通信。这也就解释了服务器端为什么要设置监听套接字和获取连接的行为。
示例代码:
connect函数介绍:
参数:
sockfd
:这是由socket
函数返回的文件描述符,代表一个套接字。addr
:这是一个指向sockaddr
结构的指针,该结构包含了服务器端的地址和端口号信息。addrlen
:这个参数指定了addr
参数所指向的地址结构体的长度。返回值:
- 成功时,返回0。
- 出错时,返回-1,并设置全局变量
errno
以指示错误类型。
3.发送消息
实例代码:
send函数介绍:
参数:
sockfd
:socket函数创建的文件描述符。表明向哪个文件描述符中写入数据。buf
:指向包含要发送数据的缓冲区的指针。len
:要发送的字节数。flags
:调用标志,通常设置为0,返回值:
- 成功时,返回实际发送的字节数。这个值可能小于请求发送的字节数,因为TCP是一个流式协议,不保证数据包的原子性。
- 出错时,返回-1,并设置
errno
以指示错误原因。
4.接收消息
示例代码:
recv函数介绍:
参数:
- sockfd:套接字文件描述符,是之前通过
socket
函数创建,表明从哪个文件中读取数据。- buf:指向接收数据的缓冲区的指针。
- len:缓冲区的大小,即可以接收的最大字节数。
- flags:一组标志,用来修改
recv
函数的行为。最常用的标志是0
,表示正常接收数据。返回值:
- 成功:返回实际接收到的字节数。如果连接正常关闭,且没有数据可读,则返回
0
。- 失败:返回
-1
,并设置errno
以指示错误原因。