摘要:
互连网技术屏蔽了底层网络硬件细节,使得异种网络之间可以互相通信。TCP/IP协议组是目前使用最广泛的网络互连协议。由于对网络的兴趣,下面一点通讯的资料与大家分享。
正文:
一、
Socket通讯
Socket提供了一个通信端口,应用程序在网络上传输/接收的信息都通过这个Socket接口来实现的。在应用开发中可以像使用文件句柄一样来对Socket句柄进行读/写操作。Windows Sockets与协议无关并向下兼容,可以使用任何底层传输协议提供的通信能力,来为上层应用程序完成网络数据通讯,而不关心底层网络链路的通讯情况,实现了底层网络通讯对应用程序的透明。在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式。
在开始使用sokcet套接字编程之前,首先必须建立以下概念。
端口
传输层与网络层在功能上的最大区别是传输层提供进程通信能力。从这个意义上讲,网络通信的最终地址就不仅仅是主机地址了,还包括可以描述进程的某种标识符。为此,TCP/IP协议提出了协议端口(protocol port,简称端口)的概念,用于标识通信的进程。
应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问之。
注意:当调用bind()的时候,不要把端口数设置的过小!小于1024 的所有端口都是保留下来作为系统使用端口的,没有root 权利无法使用。可以使用1024 以上的任何端口,一直到65535 :所可能使用的最大的端口号(当然,还要保证你所希望
使用的端口没有被其他程序所使用)。
最后注意有关
bind()
的是:有时候并不一定要调用
bind()
来建立网络连接。比如你只
是想连接到一个远程主机上面进行通讯,并不在乎你究竟是用的自己机器上的哪个端口
进行通讯(比如
Telnet
),那么你可以简单的直接调用
connect()
函数,
connect()
将自动寻找
出本地机器上的一个未使用的端口,然后调用
bind()
来将其
socket
绑定到那个端口上。
地址
网络通信中通信的两个进程分别在不同的机器上。在互连网络中,两台机器可能位于不同的网络,这些网络通过网络互连设备(网关,网桥,路由器等)连接。因此需要三级寻址:
1. 某一主机可与多个网络相连,必须指定一特定网络地址;
2. 网络上每一台主机应有其唯一的地址;
3. 每一主机上的每一进程应有在该主机上的唯一标识符。
通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。
重复服务和并发服务
在客户/服务器模式中,有两种类型的服务:重复服务和并发服务。accept()调用为实现并发服务提供了极大方便,因为它要返回一个新的套接字号,其典型结构为:
int initsockid, newsockid;
if ((initsockid = socket(....)) < 0)
error("can't create socket");
if (bind(initsockid,....) < 0)
error("bind error");
if (listen(initsockid , 5) < 0)
error("listen error");
for (;;) {
newsockid = accept(initsockid, ...) /* 阻塞 */
if (newsockid < 0)
error("accept error");
if (fork() == 0){ /* 子进程 */
closesocket(initsockid);
do(newsockid);
/* 处理请求 */
exit(0);
}
closesocket(newsockid); /*
父进程 */
}
这段程序执行的结果是newsockid与客户的套接字建立相关,子进程启动后,关闭继承下来的主服务器的initsockid,并利用新的newsockid与客户通信。主服务器的initsockid可继续等待新的客户连接请求。由于在Unix等抢先多任务系统中,在系统调度下,多个进程可以同时进行。因此,使用并发服务器可以使服务器进程在同一时间可以有多个子进程和不同的客户程序连接、通信。在客户程序看来,服务器可以同时并发地处理多个客户的请求,这就是并发服务器名称的来由
面向连接服务器也可以是重复服务器,其结构如下:
int initsockid, newsockid;
if ((initsockid = socket(....))<0)
error("can't create socket");
if (bind(initsockid,....)<0)
error("bind error");
if (listen(initsockid,5)<0)
error("listen error");
for (;;) {
newsockid = accept(initsockid, ...) /* 阻塞 */
if (newsockid < 0)
error("accept error");
do(newsockid); /* 处理请求 */
closesocket(newsockid);
}
重复服务器在一个时间只能和一个客户程序建立连接,它对多个客户程序的处理是采用循环的方式重复进行,因此叫重复服务器。并发服务器和重复服务器各有利弊:并发服务器可以改善客户程序的响应速度,但它增加了系统调度的开销;重复服务器正好与其相反,因此用户在决定是使用并发服务器还是重复服务器时,要根据应用的实际情况来定。
网络字节顺序
不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放低位字节(低价先存),有的存高位字节(高价先存)。为保证数据的正确性,在网络协议中须指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高价先存格式,它们均含在协议头文件中。
下面给出套接字字节转换程序的列表:
htons()——“Host to Network Short” 主机字节顺序转换为网络字节顺序(对无符号
短型进行操作4 bytes)
htonl()——“Host to Network Long” 主机字节顺序转换为网络字节顺序(对无符
号长型进行操作8 bytes)
ntohs()——“Network to Host Short “ 网络字节顺序转换为主机字节顺序(对无符
号短型进行操作4 bytes)
ntohl()——“Network to Host Long “ 网络字节顺序转换为主机字节顺序(对无符
号长型进行操作8 bytes)
在把数据发送到Internet 之前,一定要把它的字节顺序从主机字节顺序转换到网络字节顺序!
注:在struct sockaddr_in 中的sin_addr 和sin_port 他们的字节顺序都是网络字节顺序,而
sin_family 却不是网络字节顺序的。为什么呢?
这个是因为sin_addr 和sin_port 是从IP 和UDP 协议层取出来的数据,而在IP 和UDP
协议层,是直接和网络相关的,所以,它们必须使用网络字节顺序。然而, sin_family 域
只是内核用来判断struct sockaddr_in 是存储的什么类型的数据,并且 sin_family 永远也
不会被发送到网络上,所以可以使用主机字节顺序来存储。
阻塞与非阻塞
阻塞:一般的I/O操作可以在新建的流中运用.在服务器回应前它等待客户端发送一个空白的行.当会话结束时,服务器关闭流和客户端socket.如果在队列中没有请示将会出现什么情况呢?那个方法将会等待一个的到来.这个行为叫阻塞.accept()方法将会阻塞服务器线程直到一个呼叫到来.当5个连接处理完闭之后,服务器退出.任何的在队列中的呼叫将会被取消.
非阻塞:非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。比如调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在实际Windows网络通信软件开发中,异步非阻塞套接字是用的最多的。平常所说的C/S(客户端/服务器)结构的软件就是异步非阻塞模式的。
打个比方:
你有数个同学来访 <---> 有若干数据需要收取 ,你时不时的去门口看看,没有看到你同学的话就回客厅等待,看到同学就接到客厅来 <---> 非阻塞模式,无论收到数据与否都返回 你一直在门口等着你同学,接到后才回客厅 <---> 阻塞模式,接收到数据后才返回。
socket工作过程如下:
服务器
|
|
客户端
|
Socket()
|