天天看点

Linux系统开发: 学习Linux下网络编程

第一章 TCP网络编程 1.1 socket创建套接字

Linux系统开发: 学习Linux下网络编程

功能

创建网络套接字,用于网络通信使用,类似于文件操作的open函数。该函数在服务器和客户端都会用到。

参数

int domain :网络协议版本指定。

AF_INET        IPv4 Internet protocols

AF_INET6       IPv6 Internet protocols

int type:指定通信协议类型。

SOCK_STREAM 表明我们用的是TCP协议 (字节流)

SOCK_DGRAM 表明我们用的是UDP协议 (数据报)

int protocol:指定通信协议类型。Type参数已经指定了协议,该参数直接填0即可!

Linux系统开发: 学习Linux下网络编程

返回值

成功返回网络套接字,与open函数返回值类似。

示例

Linux系统开发: 学习Linux下网络编程
1.2 bind绑定IP-端口
Linux系统开发: 学习Linux下网络编程

创建服务器。该函数在服务器端使用。

int sockfd : 网络套接字

const struct sockaddr *addr  :填充创建服务器所需的地址信息,详细的成员看1.3章节。

socklen_t addrlen :地址长度,就是该结构体的大小。使用sizeof函数进行计算。

0表示成功,-1表示失败!

1.3 struct sockaddr地址结构体

1.3.1 结构体成员解析

在实际填充参数的过程中,struct sockaddr结构体被struct sockaddr_in结构体代替。struct sockaddr_in结构体比struct sockaddr可读性强一些,填充参数比较好理解。

struct sockaddr_in和struct sockaddr大小相同。在填充结构体的时候为了方便填充参数,使用struct sockaddr_in结构体,给函数赋值的时候需要强制转换为struct sockaddr类型的结构体。因为底层函数最终还是使用struct sockaddr类型的结构体。

struct sockaddr结构体成员:

Linux系统开发: 学习Linux下网络编程

struct sockaddr_in结构体成员:

查看IPV4协议帮助文档:# man  7  ip

Linux系统开发: 学习Linux下网络编程

1.3.2 端口号赋值

计算机数据存储有两种字节优先顺序: 高位字节优先和低位字节优先。 Internet 上数据以高位字节优先顺序在网络上传输, 所以对于在内部是以低位字节优先方式存储数据的机器, 在 Internet 上传输数据时就需要进行转换, 否则就会出现数据不一致。

普通人用的桌面电脑,只要是Intel或AMD的x86/x64架构就一定是小端字节序。

外很多ARM CPU可以选择数据指令字节序,不过通常也都是运行小端字节序(比如我们的智能手机)。

网络设备,像PowerPC核心的一些路由器,默认运行大端字节序。

下面是几个字节顺序转换函数:

·htonl(): 把 32 位值从主机字节序转换成网络字节序

·htons(): 把 16 位值从主机字节序转换成网络字节序

·ntohl(): 把 32 位值从网络字节序转换成主机字节序

·ntohs(): 把 16 位值从网络字节序转换成主机字节序

函数原型

Linux系统开发: 学习Linux下网络编程

给struct sockaddr_in结构体的端口成员赋值的时候就需要用到以上大端转小端函数进行转换!

示例:

Linux系统开发: 学习Linux下网络编程

1.3.3 IP地址赋值

struct sockaddr_in结构体存放IP地址的成员是struct in_addr 结构体类型,底层存放地址的成员是一个无符号int类型,而我们生活中的IP地址是使用xxx.xxx.xxx.xxx 这种格式表示的。比如:192.168.1.1。 在赋值的时候就需要进行将”192.168.1.1”这种格式转为无符号int类型才能进行赋值。

以下是几个IP格式转换函数:

将字符串类型IP转为in_addr_t类型(unsigned int)返回。

Linux系统开发: 学习Linux下网络编程

示例:

Serveraddr.sin_addr.s_addr = inet_addr("192.168.18.3");

使用字符串类型的IP直接给结构体成员赋值

Linux系统开发: 学习Linux下网络编程

inet_aton(“192.168.18.3”,&Clientaddr.sin_addr);

将结构体里的IP地址成员转为字符串类型返回

Linux系统开发: 学习Linux下网络编程

该函数与上面两个函数功能刚好相反。是将整型的IP转为字符串类型!

1.3.4 本地计算机大小端判断

首先说明,电脑大小端指的是一种存储模式。

为什么有大小端:

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就导致了大端存储模式和小端存储模式。

大小端定义:

大端模式(Big-endian),是指数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的高地址中。

小端模式(Little-endian),是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

直接来看一个图,详细说明大小端:

例子:int i = 0x12345678 两种模式存入内存:

Linux系统开发: 学习Linux下网络编程
4. 判断大小端的C语言代码
Linux系统开发: 学习Linux下网络编程
Linux系统开发: 学习Linux下网络编程
1.4 listen监听端口的数量
Linux系统开发: 学习Linux下网络编程

设置服务器需要监听的端口数量。决定了能够同时响应连接的服务器数量。

成功返回0,失败返回-1。

服务器创建,函数调用顺序:

Linux系统开发: 学习Linux下网络编程
示例:listen(Serverfd,10) 1.5 accept 等待客户端连接
Linux系统开发: 学习Linux下网络编程

以阻塞的形式等待客户端连接。

struct sockaddr *addr  :存放已经连接的客户端信息。传入一个结构体地址。

socklen_t *addrlen    :表示客户端的结构体大小。该大小需要我们指定,客户端连接成功然后再判断是否与填写的大小一致。

成功将返回客户端的网络套接字。错误返回-1。

Linux系统开发: 学习Linux下网络编程
1.6 connect连接服务器
Linux系统开发: 学习Linux下网络编程

连接到指定服务器。该函数在客户端使用。

int sockfd :socket函数的网络套接字。

const struct sockaddr *addr :服务器的IP地址信息。 参考:1.2节和1.3.节

socklen_t addrlen :结构体的大小。

成功返回0,错误返回-1。

Linux系统开发: 学习Linux下网络编程
1.7 send/ recv网络数据收发
Linux系统开发: 学习Linux下网络编程

客户端与服务器之间的数据收发。

const void *buf 、void *buf :读写的缓冲区。

int flags :填0。

以上两个函数可以使用write和read函数替换。

1.8 shutdown关闭连接
Linux系统开发: 学习Linux下网络编程

返回

0—成功,-1—失败。

参数how的值:

SHUT_RD:关闭连接的读这一半,不再接收套接口中的数据且留在套接口缓冲区中的数据都作废。进程不能再对套接口任何读函数。调用此函数后,由TCP套接口接收的任何数据都被确认,但数据本身被扔掉。

SHUT_WR:关闭连接的写这一半,在TCP场合下,这称为半关闭。当前留在套接口发送缓冲区中的数据都被发送,后跟正常的TCP连接终止序列。此半关闭不管套接口描述字的访问计数是否大于0。进程不能再执行对套接口的任何写函数。

SHUT_RDWR:连接的读这一半和写这一半都关闭。这等效于调用shutdown两次:第一次调用时用SHUT_RD,第二次调用时用SHUT_WR。

Linux系统开发: 学习Linux下网络编程
直接强制关闭连接示例:
Linux系统开发: 学习Linux下网络编程

1.9 查看Linux系统当前的网络连接

在/proc/net/tcp目录下面保存了当前系统所有TCP链接的状态信息。

查看示例:

Linux系统开发: 学习Linux下网络编程
Linux系统开发: 学习Linux下网络编程

说明:  这里的IP地址信息和端口号都是使用十六进制保存的。

813DA8C0:A019 49AAC3CB:0522

 查看网络状态连接:

Linux系统开发: 学习Linux下网络编程

从上面可得到的信息:

连接类型: TCP协议

本地IP地址和端口号: 192.168.61.129:40985

与其通信的远程IP地址和端口号: 203.195.170.73:1314

状态: ESTABLISHED(已建立的连接)

进程PID号与应用程序名称: 20955/./app_c

socket网络连接的状态如下

1、LISTENING状态

FTP服务启动后首先处于侦听(LISTENING)状态。

2、ESTABLISHED状态

ESTABLISHED的意思是建立连接。表示两台机器正在通信。

3、CLOSE_WAIT

   对方主动关闭连接或者网络异常导致连接中断,这时我方的状态会变成CLOSE_WAIT 此时我方要调用close()来使得连接正确关闭

4、TIME_WAIT

   我方主动调用close()断开连接,收到对方确认后状态变为TIME_WAIT。TCP协议规定TIME_WAIT状态会一直持续2MSL(即两倍的分 段最大生存期),以此来确保旧的连接状态不会对新连接产生影响。处于TIME_WAIT状态的连接占用的资源不会被内核释放,所以作为服务器,在可能的情 况下,尽量不要主动断开连接,以减少TIME_WAIT状态造成的资源浪费。

   目前有一种避免TIME_WAIT资源浪费的方法,就是关闭socket的LINGER选项。但这种做法是TCP协议不推荐使用的,在某些情况下这个操作可能会带来错误。

5、SYN_SENT状态

  SYN_SENT状态表示请求连接,当你要访问其它的计算机的服务时首先要发个同步信号给该端口,此时状态为SYN_SENT,如果连接成功了就变为 ESTABLISHED,此时SYN_SENT状态非常短暂。但如果发现SYN_SENT非常多且在向不同的机器发出,那你的机器可能中了冲击波或震荡波 之类的病毒了。这类病毒为了感染别的计算机,它就要扫描别的计算机,在扫描的过程中对每个要扫描的计算机都要发出了同步请求,这也是出现许多 SYN_SENT的原因。

根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒,TIME_WAIT状态下的socket不能被回收使用. 具体现象是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的socket, 甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket,停止服务. TIME_WAIT是TCP协议用以保证被重新分配的socket不会受到之前残留的延迟重发报文影响的机制,是必要的逻辑保证.

第二章 UDP网络编程

2.1 UDP协议创建流程

Linux系统开发: 学习Linux下网络编程
 2.2 数据报收发函数

2.2.1 recvfrom函数

UDP使用recvfrom()函数接收数据,他类似于标准的read(),但是在recvfrom()函数中要指明数据的目的地址。

Linux系统开发: 学习Linux下网络编程

成功返回接收到数据的长度,负数失败 

前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。最后两个参数类似于accept的最后两个参数(接收客户端的IP地址)。

Linux系统开发: 学习Linux下网络编程

2.2.2 sendto函数

UDP使用sendto()函数发送数据,他类似于标准的write(),但是在sendto()函数中要指明目的地址。

Linux系统开发: 学习Linux下网络编程

成功返回发送数据的长度,失败返回-1

前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。参数to指明数据将发往的协议地址,他的大小由addrlen参数来指定。

Linux系统开发: 学习Linux下网络编程
第三章 设置Socket套接字属性 3.1 函数原型介绍
Linux系统开发: 学习Linux下网络编程

sockfd:标识一个套接口的描述字。

level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。

optname:需设置的选项。

optval:指针,指向存放选项值的缓冲区。

optlen:optval缓冲区的长度。

3.2 属性功能注释

setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。

setsockopt()支持的选项定义位置:/usr/include/asm-generic/socket.h

Linux系统开发: 学习Linux下网络编程
setsockopt()支持下列选项。其中“类型”表明optval所指数据的类型。
Linux系统开发: 学习Linux下网络编程

3.3 设置socket具有广播特性

发送UDP数据报的时候,设置socket具有广播特性:(默认情况下socket不支持广播特性)

Linux系统开发: 学习Linux下网络编程

3.4 设置socket发送和接收的缓冲区大小。

系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收数据量比较大,可以设置socket缓冲区。

Linux系统开发: 学习Linux下网络编程

3.5 设置收发时限

在发送和接收过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:

Linux系统开发: 学习Linux下网络编程

3.6 允许套接字绑定已使用的端口

有时候将服务器关闭之后,端口的释放需要时间,可以设置该数据允许套接字绑定正在被占用的端口。

Linux系统开发: 学习Linux下网络编程

3.7 忽略SIGPIPE信号

往一个已经接收到FIN的套接中写是允许的,接收到的FIN仅仅代表对方不再发送数据。并不能代表我不能发送数据给对方。

往一个FIN结束的进程中写(write),对方会发送一个RST字段过来,TCP重置。

如果再调用write就会产生SIGPIPE信号。

(也就是当服务器向客户端发送数据时,客户端突然断开连接,会导致SIGPIPE信号产生,如果不处理,系统默认的处理方式就终止进程)

Linux系统开发: 学习Linux下网络编程

3.8 获取网络底层缓冲区发送剩余字节数

在网络编程时,发送方调用write(fd)将报文发送的时候实际上只是写入了内核的write buffer。接收方什么时候能收到报文是个未知数。

在某些需要同步状态机的地方,发送方最好能够确认接收方收到报文后再进行下一步动作。

linux提供了ioctl(fd, SIOCOUTQ, &count)方法来查询一个tcp socket的write buffer是否清空。发送方一般可以用这个方法来判断对端是否收到报文。当底层网卡将缓冲区的数据全部发送成功时,获取的count=0

#include <sys/ioctl.h>
 
#include <linux/sockios.h>
 
int value;
 
ioctl(client_fd,SIOCOUTQ,&value);      

3.9 获取当前网络协议底层发送与接收缓冲区大小

int sockfd;
 
/*1. 创建socket套接字*/
 
sockfd=socket(AF_INET,SOCK_STREAM,0);
 
 
 
int nRecvBuf;
 
socklen_t  len=4;
 
getsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&nRecvBuf,&len);
 
printf("接收缓冲区大小=%d\n",nRecvBuf);
 
 
 
//发送缓冲区
 
int nSendBuf;
 
getsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&nSendBuf,&len);
 
printf("发送缓冲区大小=%d\n",nSendBuf);      

Redhat6.3系统上输出结果:

接收缓冲区大小=87380

发送缓冲区大小=16384

继续阅读