C语言实现网络通信
C语言实现网络通信
TCP协议与UDP协议
相同点
:都是传输层协议
不同点
:TCP是一种面向连接的传输层协议,它能提供高可靠性的通信(数据无误、数据无丢失、数据无失序、数据无重复到达的通信)
适用情况:适合于对传输质量要求较高,以及传输大量数据的通信。在需要可靠数据传输的场合,通常使用TCP协议。
如:MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议。
UDP协议:用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
适用情况:发送小尺寸数据(如对DNS服务器进行IP地址查询时)在接收到数据,给出应答较困难的网络中使用 UDP。(如:无线网络)适合于广播/组播式通信中,通常使用TCP协议。
如:MSN/QQ等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议;流媒体、VOD、VoIP等网络多媒体服务中通常采用UDP方式进行实时数据传输。
主机字节序和网络字节序
主机字节序列分为大端字节序和小端字节序,不同的主机采用的字节序列可能不同。大端字节序是指一个整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。 在两台使用不同字节序的主机之间传递数据时,可能会出现冲突。所以,在将数据发送到网络时规定整形数据使用大端字节序,所以也把大端字节序成为网络字节序列。对方接收到数据后,可以根据自己的字节序进行转换。
大端:手机,网络
套接字通信过程中操作的数据都是大端存储的,包括:接收/发送的数据、IP地址、端口。
小端:电脑
我们使用的 PC 机,数据的存储默认使用的是小端
Linux 系统提供如下 4 个函数来完成主机字节序和网络字节序之间的转换:
主机字节序列:大端/小端
网络字节序列:大端
1 |
|
IP地址转换函数
虽然 IP 地址本质是一个整形数 ,但是在使用的过程中都是通过一个字符串来描述,下面的函数描述了如何将一个字符串类型的 IP 地址进行大小端转换:
1 | // 主机字节序的IP地址转换为网络字节序 |
参数:
af: 地址族 (IP 地址的家族包括 ipv4 和 ipv6) 协议
AF_INET: ipv4 格式的 ip 地址
AF_INET6: ipv6 格式的 ip 地址
src: 传入参数,对应要转换的点分十进制的 ip 地址: 192.168.1.100
dst: 传出参数,函数调用完成,转换得到的大端整形 IP 被写入到这块内存中
返回值:成功返回 1,失败返回 0 或者 - 1
1 |
|
参数:
af: 地址族协议
AF_INET: ipv4 格式的 ip 地址
AF_INET6: ipv6 格式的 ip 地址
src: 传入参数,这个指针指向的内存中存储了大端的整形 IP 地址
dst: 传出参数,存储转换得到的小端的点分十进制的 IP 地址
size: 修饰 dst 参数的,标记 dst 指向的内存中最多可以存储多少个字节
返回值:
成功:指针指向第三个参数对应的内存地址,通过返回值也可以直接取出转换得到的 IP 字符串 失败: NULL
注意:
下面两个函数只能用于ipv4
通常,人们习惯用点分十进制字符串表示 IPV4 地址,但编程中我们需要先把它们转化为整数方能使用,下面函数可用于点分十进制字符串表示的 IPV4 地址和网络字节序
整数表示的 IPV4 地址之间的转换:
1 |
|
套接字地址结构
通用socket地址结构
socket
网络编程接口中表示 socket 地址的是结构体 sockaddr,其定义如下:
1 |
|
sa_family 成员是地址族类型(sa_family_t) 的变量。地址族类型通常与协议族类型
对应。常见的协议族和对应的地址族如下图所示:
专用socket地址结构
TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用 socket 地址结构体,它们
分别用于 IPV4 和 IPV6:
1 | //sin_family: 地址族 AF_INET |
TCP网络编程流程
服务器端:
- 创建套接字socket()
- 填充服务器的网络信息结构体
- 绑定服务器的套接字和网络信息结构体bind()
- 将服务器的套接字设置成被动监听状态listen()
- 阻塞等待客户端的连接accept()
- 收发数据–read/write
- 关闭套接字close()
客户端:
- 创建套接字socket()
- 填充服务器的网络信息结构体
- 与服务器建立连接connect()
- 收发数据–read/wirte
- 关闭套接字close()
tcp服务端客户端编程函数详解
socket()
socket函数是用来创建一个套接字,有了套接字就可以通过网络进行数据的收发。这也是为什么进行网络通信的程序首先要创建一个套接字。创建套机子时要指定使用的服务类型,使用TCP协议选择流式服务(SOCK_STREAM)。
1 |
|
bind()
bind()方法是用来指定套接字使用的 IP 地址和端口。 IP 地址就是自己主机的地址,如果主机没有接入网络,测试程序时可以使用回环地址“127.0.0.1”。端口是一个 16位的整形值,一般 0-1024 为知名端口,如 HTTP 使用的 80 号端口。这类端口一般用户不能随便使用。其次, 1024-4096 为保留端口, 用户一般也不使用。 4096 以上为临时端口,用户可以使用。在Linux 上, 1024 以内的端口号,只有 root 用户可以使用。\
1 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
listen()
listen()方法是用来创建监听队列。 监听队列有两种,一个是存放未完成三次握手的连接,一种是存放已完成三次握手的连接。 listen()第二个参数就是指定已完成三次握手队列的长度。
1 |
|
accept()
accept()处理存放在 listen 创建的已完成三次握手的队列中的连接。每处理一个连接,则accept()返回该连接对应的套接字描述符。如果该队列为空,则 accept 阻塞。
1 |
|
connect()
connect()方法一般由客户端程序执行,需要指定连接的服务器端的 IP 地址和端口。该方法执行后,会进行三次握手, 建立连接。
1 | int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); |
send()
send()方法用来向 TCP 连接的对端发送数据。 send()执行成功,只能说明将数据成功写入到发送端的发送缓冲区中,并不能说明数据已经发送到了对端。 send()的返回值为实际写入到发送缓冲区中的数据长度。
recv()
recv()方法用来接收 TCP 连接的对端发送来的数据。 recv()从本端的接收缓冲区中读取数据,如果接收缓冲区中没有数据,则 recv()方法会阻塞。返回值是实际读到的字节数,如果recv()返回值为 0, 说明对方已经关闭了 TCP 连接。
1 |
|
close()
close()方法用来关闭 TCP 连接。此时,会进行四次挥手。
1 |
|