UNIX网络编程之第二步之嚼烂基本TCP套接字编程(以简单的TCP客户/服务器回射程序为例)

来源:互联网 发布:js图片自动轮播 编辑:程序博客网 时间:2024/04/30 00:56

讲点废话

   在博友的建议下,暂时中断了网络编程的学习,连着看了几天的linux基础视频,现在继续学习UNIX网络编程,在实践中继续学习linux,当然没看完的linux的基础视频还要继续看下去,两者交叉结合着进行。第一篇网络编程的博文只是百度了网上的各种博文,不甚了了的搭建了《UNIX网络编程》书中的环境。并敲进去第一个获得服务器时间的例子。虽然运行成功,但对于其中的原理,一窍不通,这篇博文是则主要是自己对原理的一次整理,虽然理解的可能有偏差,但基本上可以说服自己。   再有就是,受到博友的启发,抛弃UNIX网络编程一书的UNP.H头文件和包裹函数等作者自己搞出来的东东。感觉这样会限制自己以后的开发。UNP.H头文件其实也就是包含了大部分网络程序都需要的许多系统头文件,并定义了所用到的各种常值(如MAXLINE),感觉虽然它比较全,但也让自己不知道自己到底使用了那些头文件,还有就是包裹函数,这玩意好像是作者自己定义的西西(比如说Socket就是socket和错误处理结合了一下),最好还是不用。自己写比较清晰点。

言归正传,进入重点。

1/框架


首先,先给出两个框架,
这里写图片描述
框架1 基本TCP客户/服务器程序

pid_t pid;int listenfd,connfd;listenfd=socket( . . . );/*fill in sockaddr_in{} with server's well-know port*/bind(listenfd, . . . );listen(listenfd,LISTENQ);for( ; ; ){connfd=accept(listenfd,. . .);/*probably blocks*/if((pid=fork())==0){close(listenfd);/*child closes listening socket*/doit(connfd);/*process the request*/close(connfd);/*child terminates*/exit(0);}close(connfd);/*parent closes connected socket*/}
                                       框架2 典型的并发服务器轮廓

实现任何客户/服务器网络应用所需的所有基本步骤都可通过框架1来阐明,若想开发自己的应用程序,只需修改服务器对来自客户的输入的处理过程。


2/基本TCP套接字编程函数阐释

###1/socket()函数

int socket(int family,int type,int protocal)
family指明协议族(又称为协议域),可选用如下:

family 说明 AF_INET IPV4协议 AF_INET6 IPV6协议 AF_LOCAL Unix域协议 AF_ROUTE 路由套接字 AF_KEY 密钥套接字

由于我们是IPV4程序,所以这里我们就选用表中的AF_INET来作为family的值
type指明套接字类型其可选范围为

type 说明 SOCK_STREAM 字节流套接字 SOCK_DGRAM 数据报套接字 SOCK_SEQPACKET 有序分组套接字 SOCK_RAW 原始套接字

在这里我们type的值选为SOCK_STREAM,至于为什么选用如下值通过下表即明了。

KONG AF_INET AF_INET6 AF_LOCAL AF_ROUTE AF_KEY SOCK_STREAM TCP/SCTP TCP/SCTP 是 SOCK_DGRAM UDP UDP 是 SOCK_SEQPACKET SCTP SCTP 是 SOCK_RAW IPV4 IPV6 是 是

这是第三个参数protocol为0的情况下,socket函数中family和type参数组合的结果,我们的程序是TCP所以可以将第二个参数设为SOCK_STREAM,第三个参数设为0.
当然第三个参数也可以不为0,

protocol 说明 IPPROTO_TCP TCP传输协议 IPPROTO_UDP UDP传输协议 IPPROTO_SCTP SCTP传输协议

当protocol参数不为0的时候,type就可以自由多了。
总的来说根据以上设定确定下来了此次网络通信遵循的是TCP协议。此函数为客户端和服务器端都必须调用的函数(这点从框架图1也可以看出),且协议必须一致,

2/connect函数

connect函数激发出TCP三路握手过程(UDP单路连接过程),而且仅在连接建立成功或出错时返回。出错返回有以下三种情况:如果将这个链接过程想象为打电话的话可以这样说:      1/已经嘟了75s后,仍然没有人接(这是可能对方正在用其他电话在接听其他人的来电),那么此次通话失败,不会再嘟了。(这种情况对应于服务器listen()第二个参数backlog,满了)      2/你想联系的人压根就没有装电话,      3/你想联系的人在火星,地球电话根本通不到火星

connect函数导致当前套接字从closed状态(该套接字自从由socket函数创建以来一直所处的状态)转移到SYN_SENT状态,若成功则再转移到ESTABLISHED状态。若connect失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次调用connect函数。
connect(int sockfd,const struct sockaddr *servaddr,socklen_t addrlen);
sockfd是由sockfd()函数得到的网络套接字,它可以告知网络通信协议是TCP还是UDP从而告知connect()函数知道到底要进行进行几次握手连接。
知道了要进行几次连接之后还要知道与谁建立连接,因此还要输入服务器的地址(IP地址和端口号),地址结构体指针servaddr和地址结构体长度addrlen担此重任。
此函数仅为客户端所调用。

3/bind函数

bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
进程可以把一个特定的IP地址(和端口号)捆绑到它的套接字上。对于TCP客户,这就为在该套接字上发送数据指明了源IP(端口)地址。对于TCP服务器,这就限定了该套接字只接受那些目的地址为这个IP地址(和端口号)的客户的连接。TCP客户端通常不需要捆绑,由内核随机分配,而服务器则必须要捆绑并要让其端口号/IP地址为所有客户端所知道。就好比所有的人都可以访问百度,百度不需要知道用户的地址,它为所有人服务,但用户必须知道百度www.baidu.com否则它不知道把自己的请求发送到哪里。

4/listen函数

如果说bind函数通常仅有服务器调用,那么listen()函数则是必须只能由服务器调用。当socket()函数创建一个套接字时。它被认为主动套接字,它可以通过connect()函数来主动连接别人,而服务器则正是这里的别人,被连接被访问,因此listen()函数的作用就是将其定性为被动。这也就是为什么只有服务器才能调用该函数。
listen(int sockfd,int backlog);其中第二个参数为内核应该为相应套接字排队的最大连接个数,可以认为是访问服务器的最大客户机数量。
当进程调用accept时,已完成连接队列中的队头项将返回给进程,或者如果该队列为空,那么进程将被投入睡眠,直到TCP在该队列中放入一项才唤醒他。

5accept函数

int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen)
作用:返回下一个已完成(三次)连接的描述符或称之为已连接套接字。在此函数中,sockfd为服务器监听套接字。内核为每个由服务器进程接受的客户连接接受创建一个已连接套接字(也就是对于它的TCP三路握手过程已经完成)。当服务器完成对某个特定客户的服务时,相应的已连接套接字就被关闭,但监听套接字在整个有效期内都保持开放,通常我们将第二/三个参数置为空指针,因为我们对客户的身份不感兴趣。

6fork()函数

pid_t fork(void)函数在子进程中返回0,在父进程中返回子进程ID,据此可以区分子进程和父进程。通常情况并发服务器的编写就是如下一种过程:父进程调用accept之后调用fork()将已连接套接字与子进程共享,然后由子进程来处理任务,父进程则立即关闭此次的已连接套接字,回到accept()函数,阻塞于此。

0 0
原创粉丝点击