Linux下C编程(2)

【8】socket 开发 在Linux下使用C开发的应用通常都是网络应用,如果带有界面的以前都是使用QT,现在流行Android。底层应用通常大部分都是嵌入式网络应用,这些网络应用一般基于TCP/IP协议。GLIBC为网络开发提供了一个抽象概念:Socket。Socket是一个标准的UNIX文件描述符,通常一个抽象的通信端口使用这个文件描述符进行数据读写。可以通过查看进程打开的FD文件方式查看一个进程打开了多少个SOCKET,并且也这个可以通过uliimit -n来设置打开的文件描述符数量来达到限制的SOCKET数量。如下所示,可以通过进程ID号来查看描述符。

root@2003:# ps -ef|grep Sec
root      4591     1  0 Dec05 ?        00:00:00 /usr/bin/SecurityMgr
root     25247 12281  0 13:22 ttyMM0   00:00:00 grep Sec
root@2003:# ls -l /proc/4591/fd/
total 28
lr-x------  1 root root 64 Dec  9 10:03 0 -> /dev/null
lrwx------  1 root root 64 Dec  9 10:03 10 -> socket:[10468]
lrwx------  1 root root 64 Dec  9 10:03 11 -> socket:[10475]
lrwx------  1 root root 64 Dec  9 10:03 12 -> socket:[10476]
lrwx------  1 root root 64 Dec  9 10:03 13 -> socket:[10479]
lrwx------  1 root root 64 Dec  9 10:03 14 -> socket:[10484]
lrwx------  1 root root 64 Dec  9 10:03 15 -> socket:[10485]
lrwx------  1 root root 64 Dec  9 10:03 17 -> socket:[11420]
lrwx------  1 root root 64 Dec  9 10:03 19 -> socket:[11423]
lrwx------  1 root root 64 Dec  9 10:03 20 -> socket:[15691]
lrwx------  1 root root 64 Dec  9 10:03 21 -> socket:[16015]
lrwx------  1 root root 64 Dec  9 10:03 22 -> socket:[16032]
lrwx------  1 root root 64 Dec  9 10:03 23 -> socket:[4321948]
lrwx------  1 root root 64 Dec  9 10:03 25 -> socket:[4328222]
lrwx------  1 root root 64 Dec  9 10:03 3 -> socket:[10461]
lrwx------  1 root root 64 Dec  9 10:03 32 -> socket:[5274]
lrwx------  1 root root 64 Dec  9 10:03 4 -> socket:[10462]
lrwx------  1 root root 64 Dec  9 10:03 43 -> socket:[6085]
lrwx------  1 root root 64 Dec  9 10:03 44 -> socket:[6086]
lrwx------  1 root root 64 Dec  9 10:03 5 -> socket:[10463]
lrwx------  1 root root 64 Dec  9 10:03 6 -> socket:[10464]
lrwx------  1 root root 64 Dec  9 10:03 7 -> socket:[10465]
lrwx------  1 root root 64 Dec  9 10:03 8 -> socket:[10466]
lrwx------  1 root root 64 Dec  9 10:03 9 -> socket:[10467]

因为socket是一文件描述符,对socket的操作方式有点类似于文件的处理方式。只是使用不同的函数名称。首先我们要获取一个socket,对服务端来说,我们接着要将这个SOCKET与对外提供的IP/PORT进行绑定。绑定之后,服务端就开始监听这个SOCKET设备上的数据,一旦有数据从KERNEL,来到,服务端新建一个ACCEPT SOCKET文件描述符,通过这个描述符设备读取数据。socket处理方式同socket的类型相关,如SOCK_STREAM通常表示是TCP方式,而SOCK_DRAGM是UDP方式。这两种方式都是要拿到sockfd但是前者是面向连接的,如下图一所示,需要维护一个连接。而后者不需要维护一个连接,所以会少去几个步。

image

image

从上图可以看出对每一种网络应用,其基本调用过程是一样的。我们对常用的一些函数进行仔细研究一下。

1)socket函数, socket函数不管是客户端还是服务端都是第一个要调用的函数,用来获取用于通信的描述符。如下图所示,其中domain是指的协议族或者地址簇。其中协议族是以PF_开头,如PF_INET,而地址族是AF_开头,如AF_INET,这两者其实是一样,特别在WINDOWS中,但在LINUX中BSD系统都是以AF,POSIX是PF。这里常用对IPV4是AF_INET,对DOMAIN SOCKET是AF_UNIX。对面向内核的是PF_KEY。type是指的socket类型,这个类型是常量,分为多种,最常用的是三种,流式SOCK_STREAM,面向连接,可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收。数据报式SOCK_DGRAM,无连接服务,以独立包形式发送,不提供无错保证,顺序混乱。原始式SOCK_RAW,面向二层连接的开发。protocol参数一般设为0,主要是针对一个协议类型通常只有一种协议来支持。本函数调用成功,返回一个SOCKET描述符。通常称为套接字。失败是-1。

image

2)bind函数 通常的意义是给一个指定的socket描述一个名称,通常这个名称就是socket地址,这个地址结构体会根据不同的协议簇来决定地址内容,如IPV4通常是IP和端口。如下图所示,其中sockfd 是要指定的套接字描述符,my_addr是本地地址,也就是这个socket需要指定的名称。这个结构体是一个指针,因此,可以将这个指针指向不同类型的结构体,通过类型转换成sockaddr。如对IPV4是sockaddr_in,对local socket则是sockaddr_un等。addrlen则是长度。成功返回0,失败返回SOCK_ERROR.

image

2.1)struct socketadr 通用套接字接口地址结构体,这个结构比较难以使用,通常都是使用其它专门的结构体,并通过指针类型转换的方式进行使用。

image

这里其中sin_port就是端口地址,通常转为网络序就可以的。但是in_addr结构体也称网际套接口地址结构体,定义在<netinet/in.h>中,struct in_addr { in_addr_t s_addr;} 它是一个无符号32位整数。因此需要提供了一些转换函数将IP地址转成无符号函数。这三个函数是inet_aton inet_addr 或inet_ntoa.

image

如上图所示,使用这三个函数都可以将字符串如”10.192.184.2”这样的字符串转换成32位无符号数据IP地址。

2.2)多字节存储顺序,这个也就是俗称的大小头问题,如一个十六位数据0x12345678H在32位的机器中如何放在内存中呢?每们知道内存编址都是按字节作为最小单位的。存储这个值需要4个字节,那么将这个值的哪些位放到哪个字节中去呢?当然这需要有一个约定。通常有两种约定,而且这两种约定也是与机器架构有关的,如在INTERL这种架构中,大头在低位,如78存在0号56存在1号,34在2号12在3号。这就是little-endian。ARM也是这样的。另外一种是大头在高位。如IBM POWERC。网络协议中为保证不同机器之间的字节顺序不要弄错,默认为网络序,这种网络序在TCP/IP协议中采用高位先存格式,也就是big-endian。因为Socket由glibc提供,同样glibc也提供了转换函数。

image

3)listen函数 用于TCP服务器端。用来设置一个sockfd上可以排队的最大连接个数,也是用来通知内核在当前sockfd上进行监听的开始。进行listen的socket同时也是一个被动socket,主要用来充当服务器端。函数参数sockfd是前面socket函数返回的套接字。backlog表示在此描述符设备上排队的最大连接个数。

image

4)accept函数 同前面listen函数类似,只能用于TCP 服务器端。这是为什么呢?因为TCP有一个三次握手过程,因此这两个函数只出现在SOCK_STREAM类型的tcp socket中,不会出现在无连接的UDP socket中。通过listen与accept来维护连接属性是面向连接特有的函数。如下图所示,sockfd是socket函数返回的套接字,cliaddr是连接对方进程(客户)的协议地址,这是函数返回值。addrlen用来返回cliaddr套接字地址结构的长度。如果accept函数执行成功,返回值一个新的由内核自动生成的套接字,这个套接字代表与客户的TCP连接。

image

5)read函数 从一个已经处于连接的TCP套接字中读取内容,读成功,返回读取的内容长度,注意这是一个阻塞读取,线程会停在读上,直到有数据,或者如果你设置了超时参数。一次读取的值放在buffer中,并且使用count来计数。这时这个sockfd不再是socket函数返回的socket,而是accept返回的socket。最后返回值以-1作为判断,如果是-1则无数据阻塞,否则还可以继续读下去。

image

6)write函数 将内容写入一个已经处于连接状态的套接字中,写成功,返回写入的内容长度。同前面read函数一样。

image

7)close函数 缺省功能是将套接字设置已关闭标志,并返回到进程,这个套接字不再为进程所用。它可以关闭accept函数和socket函数产生的sockfd.

image

8)connect 函数,用于TCP客户端,用来向服务端进行连接的函数。如下图所示,其中sockfd是客户端首先使用socket函数获取得的sockfd,sockaddr是将要连接的服务端的地址和端口。

image

你可能感兴趣的:(编程,linux,职场,休闲)