写在开头:
在上一节中我们学习了一些基础的用来支持网络编程的API,包括“套接字的地址结构”、“字节排序函数”等。这些API几乎是所有的网络编程中都会使用的一些,对于我们正确的编写网络程序有很大的作用。在本节中我们会介绍编写一个基于TCP的套接字程序需要的一些API,同时会介绍一个完整的TCP客户服务器程序,虽然这个程序功能相对简单,但确包含了一个客户服务器程序所有的步骤,一些复杂的程序也都是在此基础上进行扩充。在后面随着学习的深入,我们会给这个程序添加功能。
下面我们首先给出这个程序实例,然后根据程序分析其中用到的套接字函数,这些套接字函数也是其他的TCP网络编程中都会使用到的,包括像:socket 函数,connect 函数,bind 函数,listen 函数,accept函数,fork和exec函数等,其实在之前(一)中已经使用了,而且也有了部分的介绍,这里将会给出详细的说明 。
------------------------------------------------------------------------------------------------------------------------------
TCP客户服务器程序
我们这里的服务器程序是一个回射服务器,实现以下功能:
(1) 客户从标准输入中读入一行文本,然后将文本写给服务器;
(2) 服务器从网络输入读入这行文本,并回射给用户;
(3) 客户从网络输入中读入这行文本,并显示在标准输出中。
功能的模型如下面所示:
下面是具体的服务器端和客户端的程序,可以在我们所下载的源码tcpcliserv/tcpcli01.c 和tcpcliserv/tcpserv01.c中找到
下面是服务器端代码:
#include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); for ( ; ; ) { clilen = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); if ( (childpid = Fork()) == 0) { /* child process */ Close(listenfd); /* close listening socket */ str_echo(connfd); /* process the request */ exit(0); } Close(connfd); /* parent closes connected socket */ } }
服务器会调用下面的lib/str_echo.c中的程序
#include "unp.h" void str_echo(int sockfd) { ssize_t n; char buf[MAXLINE]; again: while ( (n = read(sockfd, buf, MAXLINE)) > 0) Writen(sockfd, buf, n); if (n < 0 && errno == EINTR) goto again; else if (n < 0) err_sys("str_echo: read error"); }
下面是客户端的程序:
#include "unp.h" int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) err_quit("usage: tcpcli <IPaddress>"); sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); Connect(sockfd, (SA *) &servaddr, sizeof(servaddr)); str_cli(stdin, sockfd); /* do it all */ exit(0); }进入到tcpcliserv/ 然后执行
# make tcpserv01 tcpcli01
然后在两个进程中分别将服务器和客户端运行起来,如下所示在客户端可以看到我们输入一行之后按回车会显示相同的从服务器端传回来的文本。--------------------------------------------------------------------------------------------------------------------------
至此这个程序运行起来了,下面我们开始介绍服务器程序和客户端程序中的套接字函数是怎样将整个功能完成的,这里的函数包括:socket 函数,connect 函数,bind 函数,listen 函数,accept函数,fork和exec函数 等。首先我们给出整个函数被调用的一个流程图,这个流程图是根据tcp协议建立起来的:
这就是整个函数被调用过程的一个流程以及完成的功能。下面我们详细的介绍这些函数的用法:
socket 函数
socket 函数是进程执行网络I/O操作第一件需要做的事情,通过调用socket 函数来指定期望的通信协议类型并返回一个套接字描述符用来标识这个连接,套接字描述符,简称sockfd,是一个小的非负整数值类似于文件描述符。用法:
#include <sys/socket.h> int socket ( int family, int type, int procotol); /* 返回:若创建成功返回一个非负sockfd, 否则返回 -1 */
例如上面服务器端程序tcpserv1.c line11中的一句:
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
这里S 大写是另外一个socket 的封装函数(原书称为包裹函数)主要是添加了对错误的处理,其中主要的函数就是socket,包括下面的一些函数,所以我们认为他们相同。
参数:
family: 代表的是协议族,指明该套接字在网络层使用什么来输出,包括:AF_INET(IPv4), AF_INET6(IPv6), AF_LOCAL(Unix 域协议), AF_ROUTE(路由套接字), AF_KEY(秘钥套接字)等
type: 指明套接字使用的数据流的类型,包括 SOCK_STREAM(字节流套接字), SOCK_DGRAM (数据报套接字),SOCK_SEQPACKET(有序分组套接字), SOCK_RAW(原始套接字)等;
protocol: 指明的套接字使用的传输层协议类型,包括:IPPROTO_TCP(TCP传输协议) IPPROTO_UDP(UDP传输协议),IPPROTO_SCTP(SCTP传输协议等);一般为了省事直接将这个字段置0,由给定的family和type来决定使用什么协议。
2015/02/03 于南京 CSDN 如需转载请注明地址谢谢:http://blog.csdn.net/michael_kong_nju/article/details/43457393