Linux 软件系列之十四——网络编程

1.什么是socket:套接字是网络通信中应用程序对应的进程和网络协议之间的接口。是属于传输层的部分。套接字在所有的网络操作系统中都是必不可少的。


2.socket的类型

流式套接字(SOCK_STREAM)

       流式套接字可以提供可靠的,面向连接的通信。流式套接字对应的是TCP协议,TCP协议保证了数据传输的正确性和顺序性。如:通过流式套接字发送了顺序数据:1,2,3,那么,数据到达远程时的顺序也是1,2,3。

数据报套接字(SOCK_DGRAM)

       提供不可靠的,无连接通信。数据报套接字对应的是UDP协议。UDP协议不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。这就意味着:1、你发送的数据可能不会到达。2、发送的数据可能以不同的顺序到达。3、发送的数据可能存在错误。优点是数据传输速度快。

原始套接字(SOCK_RAW)

       原始套接字主要用于一些协议的开发,可以进行比较底层的操作,它的功能强大,但使用不如上面两种套接字方便。

3.TCP/IP端口号

       如果把IP地址比作一间房子 ,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口 可以有65536个之多!端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535。

       网络程序之间的连接要使用TCP/IP,应用程序必须提供源和目标应用程序的IP地址和端口号,端口号提供了一个发送的位置。每个端口由一个唯一的编号来标识。1024以下的端口由internet编号分配机构(IANA)分配。

服务器端需要绑定端口号,如果不绑定端口号,该程序不知道接收哪个端口号的数据。

客户端一般不用绑定,因为客户端发送成功数据后(假设从A端口发送),那么接收数据它也会从这个端口接收,相当于系统自动给它分配一个端口用。

4.127.0.0.1:是回环地址,指本地机,一般用来测试使用。无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,不进行任何网络传输。(使用自己机子的ip也可以)

IP地址转换函数

inet_addr():将一个用数字和点表示的IP地址的字符串转换成一个无符号的长整型数据。

inet_aton():将一个无符号的长整型数据转换成用数字和点表示的IP地址字符串。




5.字节序:不同类型的CPU主机中,内存存储多字节整数序列有两种方法:

小端序(little-endian):又称为主机字节序。低字节保存在前面的内存单元中。         

大端序(big-endian):又称为网络字节序。高字节保存在前面的内存单元中。

字节排序函数(h:host,n:network)

htonl:将主机字节顺序转换为网络字节顺序。对无符号长型操作。

ntohl:将网络字节顺序转换为主机字节顺序。对无符号长型操作

htons:将主机字节顺序转换为网络字节顺序。对无符号短型操作

ntohs:将网络字节顺序转换为主机字节顺序。对无符号短型操作

6.UDP套接字

UDP套接字的实现基于TCP/IP协议,面向无连接的通信模式。UDP套接字不能提供可靠的数据传输,但UDP套接字的实现简单,并且传输效率高,因而得到广泛的应用。如:DNS(域名系统)、NFS(网络文件系统),SMMP(简单网络管理协议)


UDP套接字分为服务器端和客户端两部分:

1)服务器端的步骤:

       a、建立UDP套接字

       b、绑定套接字到特定的地址

       c、等待并接受客户端的信息

       d、处理客户请求

       e、发信息回客户端

       f、关闭套接字

2)客户端步骤

       a、建立UDP套接字

       b、发送信息给服务器端

       c、接收来自服务器端的信息

       d、关闭套接字

7.TCP套接字

TCP套接字的实现基于TCP/IP协议,是带连接的通信模式。是一个可靠的的传输协议。在使用TCP套接字进行数据传输的过程中有三次握手操作:

1)建立连接时,客户端发送SYN(握手信号)包到服务器,并进入SYN_SEND状态,等待服务器确认。

2)服务器收到SYN包,必须确认客户的SYN,同时,自己也要发送一个SYN+ACK包给客户端,此时,服务器进入SYN_RECV状态。

3)客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,此包发送完毕后,客户端和服务器进入ESTABLISHED(建立连接)状态,完成三次握手。

实现TCP套接字的基本步骤如下,分为服务器端和客户端两部分。

1)服务器端:

a、创建套接字

b、捆绑套接字

c、设置套接字为监听模式,进入被动接收连接请求状态。ddos

d、接收请求,建立连接。

e、发送接收数据(send()、recv())。

f、终止连接

2)客户端步骤:

a、创建套接字。

b、请求与远程服务器的连接。

c、发送接收数据。

d、终止连接

8.socket函数




建立套接字描述符与相应的套接字地址对应起来。意味着该套接字只接受来自绑定的协议端口(IP地址和TCP端口)的数据。

端口号和ip地址都要是网络字节顺序。

9.TCP协议涉及的函数



为了接受连接,先用socket()创建一个套接口,然后用listen()为申请进入的连接建立一个后备日志,然后便可用accept()接受连接了。listen()仅适用于支持连接的套接口,如SOCK_STREAM类型的。套接口s处于一种“变动”模式,申请进入的连接请求被确认,并排队等待被接受。这个函数特别适用于同时有多个连接请求的服务器;如果当一个连接请求到来时,队列已满,那么客户将收到一个WSAECONNREFUSED错误。

listen函数的第一个参数是SOCKET类型的,该函数的作用是在这个SOCKET句柄上建立监听,至于有没有客户端连接进来,那就需要用accept函数去进行检查了,accept函数的第一个参数也是SOCKET类型,跟listen函数是同一个socket句柄,所以一般accept都是放在一个线程中不停的调用,因为你不知道什么时候会有客户连接进行来。

aceept()函数返回一个新的套接字来接受数据,原来的套接字继续监听。listen的socket对象理解成火车站,aceept的socket对象理解成要接的旅客。要接到旅客,首先要到火车站去等待(即listen的socket对象),旅客有没有到,你要不停的在出口处查看有没有需要接的旅客(即aceept在检查到新连接产生的socket对象)。接到accept的对象才是需要处理的对象(这个对象和listen的对象不同)。

listen的寿命就是服务器的寿命,listen的socket销毁(即火车站被销毁),服务器与客户端就完全断开连接(去接旅客的人和乘火车来的人都会无法见面),是否有旅客要靠不停循环的去检测,为了不阻塞主线程,检测的函数(即accept)要放在线程中。至于accept是怎么检测到连接的,那是tcp/ip协议实现的。




10.UDP协议涉及的函数



阻塞socket和非阻塞socket

读操作
对于阻塞的socket,当socket的接收缓冲区中没有数据时,read调用会一直阻塞住,直到有数据到来才返

回。当socket缓冲区中的数据量小于期望读取的数据量时,返回实际读取的字节数。当sockt的接收缓冲

区中的数据大于期望读取的字节数时,读取期望读取的字节数,返回实际读取的长度。

对于非阻塞socket而言,socket的接收缓冲区中有没有数据,read调用都会立刻返回。接收缓冲区中有
数据时,与阻塞socket有数据的情况是一样的,如果接收缓冲区中没有数据,则返回错误号为

EWOULDBLOCK,
表示该操作本来应该阻塞的,但是由于本socket为非阻塞的socket,因此立刻返回,遇到这样的情况,可

以在下次接着去尝试读取。如果返回值是其它负值,则表明读取错误。
因此,非阻塞的rea调用一般这样写:

if ((nread = read(sock_fd, buffer, len)) < 0)
{
if (errno == EWOULDBLOCK)
{
return 0; //表示没有读到数据
}else return -1; //表示读取失败
}else return nread;读到数据长度

写操作

对于写操作write,原理是类似的,非阻塞socket在发送缓冲区没有空间时会直接返回错误号EWOULDBLOCK,
表示没有空间可写数据,如果错误号是别的值,则表明发送失败。如果发送缓冲区中有足够空间或者
是不足以拷贝所有待发送数据的空间的话,则拷贝前面N个能够容纳的数据,返回实际拷贝的字节数。

而对于阻塞Socket而言,如果发送缓冲区没有空间或者空间不足的话,write操作会直接阻塞住,如果有

足够空间,则拷贝所有数据到发送缓冲区,然后返回.
非阻塞的write操作一般写法是:

int write_pos = 0;
int nLeft = nLen;

while (nLeft > 0)
{
int nWrite = 0;
if ((nWrite = write(sock_fd, data + write_pos, nLeft)) <= 0)
{
if (errno == EWOULDBLOCK)
{
nWrite = 0;
}else return -1; //表示写失败
}
nLeft -= nWrite;
write_pos += nWrite;
}
return nLen;

建立连接
  阻塞方式下,connect首先发送SYN请求到服务器,当客户端收到服务器返回的SYN的确认时,则
connect返回.否则的话一直阻塞.
非阻塞方式,connect将启用TCP协议的三次握手,但是connect函数并不等待连接建立好才返回,而是
立即返回。返回的错误码为EINPROGRESS,表示正在进行某种过程.

接收连接
对于阻塞方式的倾听socket,accept在连接队列中没有建立好的连接时将阻塞,直到有可用的连接,才返回。
非阻塞倾听socket,在有没有连接时都立即返回,没有连接时,返回的错误码为EWOULDBLOCK,表示本来应
 该阻塞。

无阻塞的设置方法

方法一:fcntl
int flag;
if (flag = fcntl(fd, F_GETFL, 0) <0) perror("get flag");
flag |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flag) < 0)
perror("set flag");

方法二:ioctl

int b_on = 1;
ioctl (fd, FIONBIO, &b_on);

11.高级编程的必要性:应用程序中同时处理多路的输入输出时,如果采用阻塞模式,那么将得不到预期的目的;如果采用非阻塞模式,对多个输入进行轮询,就太浪费时间;如果设置多个进程,将使程序变的更加复杂,而且,在多个客户端存在时使用是不合理的。最好的办法是使用I/O多路复用。

I/O多路复用的思想:I/O多路复用能够在指定的时间内对多个客户端进行监听,是否有数据包到达,也能监听发送和错误的数据包,但通常监听是否有数据包到达。通常情况下,非阻塞会和I/O多路复用一起使用。

使用I/O多路复用的步骤:

1)设置网络通信是非阻塞通信。

实现方法有两种:

a、调用fcntl函数

b、调用ioctl函数

2)使用select函数对I/O进行查询。




描述符集合操作:(fd_set)是一组文件描述符(fd)的集合。将要查询的套接字放到描述符集合中,然后对描述符集进行操作。由于fd_set类型的长度在不同的平台上不同,因此,应该使用一组标准的宏定义来处理此类变量。

1)FD_ZERO(fd_set *fdset):将指定的文件描述符清空。后面产生的套接字的值会比前面的大,所以用最大的文件描述符值+1来作为参数传入。

2)FD_SET(int fd, fd_set *fdset):在文件描述符集合中增加一个新的文件描述符。

3)FD_CLR(int fd, fd_set *fdset):在文件描述符集合中删除一个文件描述符。

4)FD_ISSET(int fd, fd_set *fdset):测试指定的文件描述符是否在该集合中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值