您的位置:首页 > 其它

面向连接的协议 [2 ]

2010-08-29 16:31 218 查看
面向连接的协议—服务器端



由图我们可以看出,服务器与客户的区别在于:

服务器必须进行套接口绑定。因为如果服务器没有地址,客户就无法进行连接。
S3
的作用在于告诉内核,在某个套接口上监听并接收请求。

服务器需要监听连接。

下面,我们介绍一下有关的函数,
int
listen(int sockfd, int backlog);

s:
用于监听的套接口。Backlog:
监听队列(the
queue of pending connections)
的最大长度。
2.2
之后的版本中,backlog
的值只包括与监听套接口建立了的连接数。
成功:返回0
,否则,返回
-1
,失败原因 在
errno
中。

监听队列



如上图,对listen
函数调用成功后,在Linux
内核中建立了一个监听队列,它的长度由
backlog
决定。
图中正在处理请求1

第2

第5
的连接请求被挂起,第6
个请求正在插入队列,同时内核还收到了7,8,9

连接。更多的连接正在到达的路上。

accept
函数
int
accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:
由socket()
创建,已经用bind()
绑定了一个本地地址,正在监听连接的套接口的描述符。
addr:
用于存放通信的另一端(客户)的套接口地址的变量。
addrlen:
既是输入参数又是输出参数。作为输入参数,它指定了addr
的最大长度。作为输出参数,当函数返回后,它代表了地址的实际长度。
如果函数调用成功,它返回一个新的套接口描述符。否则,返回
-1,
错误的原因记录在errno

中。在新的套接口代表服务器端与客户端进行通信。当客户请求处理完毕时,这个由accept
返回的套接口将关闭。而为了能够接收新的用户请求,在此期间,sockfd
指定的,也就监听套接口,一直处于打开状态。
小节:这里提到了两种套接口,一种是监听套接口,数量只有一个,作用是接收用户请求并创建新的套机口(通信套接口)。另一种,accept
返回的通信套接口,它同用户进程连接,并实现数据的读写操作。
下面是一个简单的TCP/IP
服务器程序,用来代替
daytime
服务器。

//server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <time.h>
static void bail (const char* on_what)
{
fprintf (stderr, "%s error:%s", on_what, strerror(errno));
exit(1);
}
int main (int argc, char** argv)
{
int z;
char* srvr_addr = NULL;
char* srvr_port = NULL;
struct sockaddr_in adr_srvr;
struct sockaddr_in adr_clnt;
int len_inet = sizeof(adr_srvr);
int s;//监听socket
int c;//客户端socket
int n;
time_t td;//当前的日期和时间
char dtbuf[128];//日期/时间信息
//参数过滤
if (argc != 3) {
printf ("/tFormat: cmd IP_addr port/n");
exit(1);
}
//生成监听套接口
s = socket (PF_INET, SOCK_STREAM,0);
if (s == -1) {
bail ("socket()");
}
//生成地址结构
memset (&adr_srvr, 0, sizeof(adr_srvr));//很重要
adr_srvr.sin_family = AF_INET;
adr_srvr.sin_port = htons (atoi (argv[2]));//注意这里函数要用对
adr_srvr.sin_addr.s_addr = inet_addr (argv[1]);
if (adr_srvr.sin_addr.s_addr == INADDR_ANY) {
bail ("inet_addr()");
}
//绑定绑定监听套接口
z = bind (s, (const struct sockaddr*)&adr_srvr, sizeof(adr_srvr));
if (z == -1) {
bail ("bind ()");
}
//开始监听
z = listen (s, 10);
if (z == -1) {
bail ("listen ()");
}
//开始服务循环
while (1) {
printf ("等待连接/n");
/* 等待连接 */
len_inet = sizeof(adr_clnt);
c = accept (s, (struct sockaddr*)&adr_clnt, (socklen_t*)&len_inet); //阻塞在此处直到有连接
if (c == -1) {
bail ("accept()");
}
//生成时间戳
time (&td);
n = (int) strftime (dtbuf, sizeof(dtbuf),
"%A %b %d %H:%M:%S %Y/n", localtime (&td));
//printf ("时间结果:%s/n", dtbuf);
//将结果返还给用户
z = write (c, dtbuf, n);
if (z == -1)
bail ("write()");
printf ("一次通信完成/n");
//关闭通信套接字
close (c);
}
return 0;
}
$make server
$ ./server 192.168.1.230 9099& //&表示在后台运行
$netstat -n -a -p –tcp//查看你的程序是否处在监听状态
tcp 0 0 192.168.1.230:9099 *:* LISTEN 8636/server
$telnet 192.168.1.230 9099
一次通信完成
等待连接
Connected to 192.168.1.230.
Escape character is '^]'.
Sunday Aug 29 15:57:45 2010
Connection closed by foreign host.
修改 daytime.c程序
adr_srvr.sin_port = sp->s_port;
修改为:
adr_srvr.sin_port = htons (atoi("9099"));
$make daytime
$./daytime 192.168.1.230 //我们用9090端口
一次通信完成
Datetime is Sunday Aug 29 16:00:37 2010
等待连接


成功。
扩展:
TCP/IP
的1~1024
端口都被保留起来了,一般自己写的服务器程序使用>=1024
的端口
0:
表示通配端口,系统会自动的为它分配一个没有使用的端口。
INADDR_ANY:
通配IP
地址。
严格意义上讲,服务器的地址包括
IP
地址和端口号两部分。
一个服务器地址不能完全通配。服务器地址完全通配,当且仅当
Ip
地址和端口号都通配。
这样,在指定了特定端口号后,IP
地址部分可以通配(INADDR_ANY),
这使得服务器可以接受客户发往任何一个本地接口的连接请求。当一个主机有多个IP
接口时(如网关),这极为重要。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: