本篇介绍的内容虽为linux下的c语言socket网络编程模型,但是其在windows下一样适用。甚至不同的编程语言,如java、python等也适用,只是包装的更加友好一些而已。话不多说,开始记录。
网络基础
下表是网络模型与协议的对应关系:
OSI参考模型 | TCP/IP参考模型 | 网络协议 |
---|---|---|
应用层 | 应用层 | Telnet/HTTP/FTP/TFTP/NFS/SMTP/SNMP等 |
表示层 | ||
会话层 | ||
传输层 | 传输层 | TCP、UDP |
网络层 | 网络层 | ICMP、IP、IGMP、ARP |
数据链路层 | 链路层 | Ethernet、Token Ring等 |
物理层 |
名词解释
- IP:Internet协议。
- ICMP:网际控制报文协议,ping命令就是用此协议实现。
- ARP:地址解析协议。
- TCP:传输控制协议。
- UDP:用户数据报协议。
从上表中看出TCP和UDP是属于传输层的协议,它们是网络编程的基础,下面重点介绍一下。
TCP
TCP协议对建立网络上用户进程之间的对话负责,它确保进程之间的可靠通信,提供如下功能:
- 监听输入对话建立请求;
- 请求另一网络站点对话;
- 可靠的发送和接收数据;
- 适度的关闭对话;
使用TCP协议需要建立客户端与服务器的连接,建立连接时需要三次握手:
- 客户端发送连接请求;
- 服务端响应连接请求;
- 客户端确认响应;
我理解这个过程有点像我们打电话:你好我找B;你好我是B;你好B我是A。
然后可以正常对话了。这样不至于把天聊死。
说完TCP建立连接的三次握手,就不能不说断开连接时的四次挥手:
- 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送;
- 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1;
- 服务器B关闭与客户端A的连接,发送一个FIN给客户端A;
- 客户端A发回ACK报文确认,并将确认序号设置为收到序号加1;
我理解这个过程有点像我们打电话:B开着免提正在跟A说着话,这时:我A要挂了啊有话快说;既然你A要挂肯定是不想跟我B说话了我先关免提;我B把没说完的话说完后挂掉;A挂掉。
我们实际编程时是感受不到这三次握手和四次挥手的,我们只管调用connect和close就行。但是我们一般要遵循一个原则:客户端主动关闭
。因为主动关闭的一方最终是要进入time_wait
状态,这种状态下端口不能复用。如果服务端主动关闭的话,可能会造成短时间内time_wait状态过多而无法分配新连接。
UDP
UDP提供不可靠的非连接型传输层服务,它允许在源和目的地之间的数据传输,而不必在传输数据之前建立连接。它主要用于那些非连接型的应用程序。
由于UDP直接发送数据,所以开销小、速度快。适用于流动音频、视频、广播等实时数据传输。
下表是TCP和UDP的区别:
TCP | UDP |
---|---|
面向连接 | 面向非连接 |
传输大量的数据 | 即时传输少量数据 |
可靠的 | 不可靠的 |
应用层协议就不说了,他们其实都是TCP、UDP协议的socket实现。
网络编程基础
socket基础
网络编程是通过socket接口实现的,下面三条是抄来的,留待以后理解:
- 网络层的
ip地址
可以唯一标识网络中的主机 - 传输层的
协议+端口
可以唯一标识主机中的应用程序 - 三元组
(ip地址,协议,端口)
标识网络的进程
常用socket类型:
- 流式套接字
SOCK_STREAM
:提供可靠的、面向连接的通讯流。使用TCP协议,保证了数据传输的正确性的顺序性。 - 数据报套接字
SOCK_DGRAM
:定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,不保证可靠、无差错,使用UDP协议。
地址结构
1 | struct sockaddr |
sa_family:地址族,采用AF_XXX的形式,一般为AF_INET
。
sa_data:14字节的特定协议地址。
实际编程中一般不直接针对sockaddr结构操作,而是使用sockaddr_in数据结构:1
2
3
4
5
6
7
8
9
10
11struct sockaddr_in
{
short int sin_family; //地址族AF_INET
unsigned short int sin_port; //端口
struct in_addr sin_addr; //ip地址
unsigned char sin_zero[8]; //0
}
struct in_addr
{
unsigned long s_addr; //4字节的ip地址
}
地址转换1
2int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
函数中a代表ascii,n代表network。
字节序转换
网络字节序是大端big endian
,即高字节存在低地址。而主机字节序有可能大端也有可能小端,这样就要进行字节序转换。
- htons:把unsigned short类型从主机序转换成网络序;
- htonl:把unsigned long类型从主机序转换成网络序;
- ntohs:把unsigned short型从网络序转成主机序;
- ntohl:把unsigned long型从网络序转成主机序;
ip和主机名
1 | struct hostent *gethostbyname(const char *hostname); |
常用函数
进行socket编程的常用函数有:
- socket:创建一个socket
- bind:绑定ip地址和端口号到socket
- connect:与服务端建立连接
- listen:设置能处理的最大连接数
- accept:服务端用来接收socket连接
- send:发送数据
- recv:接收数据
网络应用开发流程
TCP服务端
- 创建socket,socket()
- 绑定socket,bind()
- 设置允许的连接数,listen()
- 接收客户端连接,accept()
- 收发数据,send()/recv(),或read()/write()
- 关闭网络连接,close()
TCP客户端
- 创建socket,socket()
- 设置服务端地址
- 连接服务端,connect()
- 收发数据
- 关闭网络连接
UDP服务端
- socket()
- bind()
- recvfrom()
- sendto()
- close()
UDP客户端
- socket()
- sendto()
- recvfrom()
- close()
函数说明
int socket( int domain, int type, int protocol);
- 功能:创建套接字
- 参数:
domain
指明所使用的协议族;type
指定套接字的类型;protocol
通常赋值 “0” - 返回值:成功返回正整数表示socket文件描述符,失败返回-1
bind()
connect()
listen()
accept()
send()
recv()
sendto()
recvfrom()