linux网络编程入门

本篇介绍的内容虽为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协议对建立网络上用户进程之间的对话负责,它确保进程之间的可靠通信,提供如下功能:

  1. 监听输入对话建立请求;
  2. 请求另一网络站点对话;
  3. 可靠的发送和接收数据;
  4. 适度的关闭对话;

使用TCP协议需要建立客户端与服务器的连接,建立连接时需要三次握手:

  1. 客户端发送连接请求;
  2. 服务端响应连接请求;
  3. 客户端确认响应;

我理解这个过程有点像我们打电话:你好我找B;你好我是B;你好B我是A。然后可以正常对话了。这样不至于把天聊死。

说完TCP建立连接的三次握手,就不能不说断开连接时的四次挥手:

  1. 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送;
  2. 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1;
  3. 服务器B关闭与客户端A的连接,发送一个FIN给客户端A;
  4. 客户端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接口实现的,下面三条是抄来的,留待以后理解:

  1. 网络层的ip地址可以唯一标识网络中的主机
  2. 传输层的协议+端口可以唯一标识主机中的应用程序
  3. 三元组(ip地址,协议,端口)标识网络的进程

常用socket类型:

  • 流式套接字SOCK_STREAM:提供可靠的、面向连接的通讯流。使用TCP协议,保证了数据传输的正确性的顺序性。
  • 数据报套接字SOCK_DGRAM:定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,不保证可靠、无差错,使用UDP协议。

地址结构

1
2
3
4
5
struct sockaddr
{
u_short sa_family;
char sa_data[14];
}

sa_family:地址族,采用AF_XXX的形式,一般为AF_INET
sa_data:14字节的特定协议地址。

实际编程中一般不直接针对sockaddr结构操作,而是使用sockaddr_in数据结构:

1
2
3
4
5
6
7
8
9
10
11
struct 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
2
int 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
2
3
4
5
6
7
8
9
10
11
struct hostent *gethostbyname(const char *hostname);
struct hostent
{
char *h_name;
char *h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
//主机的第一个ip地址
#define h_addr h_addr_list[0]

常用函数

进行socket编程的常用函数有:

  • socket:创建一个socket
  • bind:绑定ip地址和端口号到socket
  • connect:与服务端建立连接
  • listen:设置能处理的最大连接数
  • accept:服务端用来接收socket连接
  • send:发送数据
  • recv:接收数据

网络应用开发流程

TCP服务端

  1. 创建socket,socket()
  2. 绑定socket,bind()
  3. 设置允许的连接数,listen()
  4. 接收客户端连接,accept()
  5. 收发数据,send()/recv(),或read()/write()
  6. 关闭网络连接,close()

TCP客户端

  1. 创建socket,socket()
  2. 设置服务端地址
  3. 连接服务端,connect()
  4. 收发数据
  5. 关闭网络连接

UDP服务端

  1. socket()
  2. bind()
  3. recvfrom()
  4. sendto()
  5. close()

UDP客户端

  1. socket()
  2. sendto()
  3. recvfrom()
  4. close()

函数说明

int socket( int domain, int type, int protocol);

  • 功能:创建套接字
  • 参数:domain指明所使用的协议族;type指定套接字的类型;protocol通常赋值 “0”
  • 返回值:成功返回正整数表示socket文件描述符,失败返回-1

bind()
connect()
listen()
accept()
send()
recv()
sendto()
recvfrom()