锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 在线教育 / SOCKET网络通信开发公开课 / Sockets-Servers Socket服务器
服务方向
人工智能数据处理
人工智能培训
kaldi数据准备
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
kaldi开发技术服务
软件开发
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发
联系方式
固话:0371-63888850
手机:138-0381-0136
Q Q:396806883
微信:ryysoft

3.19 Sockets-Servers Socket服务器


要点:

  • 基本的服务器流程
  • bind()
  • listen()
  • accept()
  • Unix服务器
  • 互联网服务器

3.19.1 基本的服务器流程

  • 首先要创建个socket

sd=socket(domain,type,protocol);
指定通信域,类型和协议

  • 绑定地址和协议

bind(sd,&serv_addr,addrlen);

  • 监听连接

listen(sd,backlog)

  • 当客户端尝试连接服务器时,操作系统通知进程来接收连接

confd=accept(sd,&serv_addr,addrlen);

  • 随后就可以通过socket发送和接收数据:

read(confd,buf,nbytes);
write(cnofd,buf,nbytes);
上面是使用UNIX的I/O读写方式,使用发送和接收函数在随后讨论

  • 在客户端连接使用结束时,要释放socket

close(confd);

  • 在所有连接结束时,应该用下面函数之一来释放socket

close(sd);//注意,这里是服务器的socket,bind用的socket
shutdown(sd,how);

  • 注意:对于无连接的协议,比如UDP,listen()和accept()步骤不需要;输入/输出操作使用sendto(),recvfrom()。

3.19.2 bind()函数

  • socket()函数创建了一个socket,socket自己不可能关联到地址上,比如一个服务端口。对于服务器端,需要使用bind()函数;对于客户端,地址关联用connect()(在底层实现上,它和bind是类似的)。
  • bind函数的原型是:

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sd,struct sockaddr*my_addr,socklen_t addrlen);
参数和connect()函数类似,除了const修饰符从地址结构上被删除了,这意味着这个参数能修改。然而,在些情况下,Linux好象不遵守通常的函数原型,虽然这些原型在其它系统上使用,实际上你能检查到这个地址并没有实际上修改过。

  • bind()函数容易混淆的地方是,它经常描述为“指定个名称”到socket上。实际上这个函数和“名称”(比如redhat.com)没关系;它指派地址和协议。
  • 如果想bind()到指定的IP地址和服务端口上,示例代码如下:
int sd;
short int  port=50000;
struct  sockaddr_in addr;
struct in_addr  inaddr;
…
sd=socket(PF_INET,SOCK_STREAM,0);
memset(addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(port);
addr.sin_addr.s.addr=inet_addr(“192.168.1.1”);
bind(sd,(struct  sockaddr*)&addr,sizeof(struct sockaddr));
出于简洁的目的,我们使用了旧风格的inet_addr()函数,而不是inet_aton()。同样注意到我们谨慎地把地址结构体内存清0,确保服务端口以合适的网络顺序保存。

  • 然而,内核有可能自动选择服务端口和IP地址。
  • 如果我们选择服务端口为0,内核会选择一个可用的临时(动态的)端口。如果我们想知道我们选择哪个端口了,我们不能在bind()后检查地址结构,因为这个地址结构对象不会修改的。要想实现这个检查,要这样用:
struct  sockaddr_in check;
int check_len;
…
bind(…);
getsockname(sd,(struct  sockaddr*)&check,&check_len);
printf(“selected port %d\n”,ntohs(check.sin_port);
  • 然而,服务器端很少这样要求,因为客户端必须知道服务器使用的端口,知道了才能连接上。换个说法,客户端同样能够调用bind()这样的操作;实际上是connect()函数隐含做了这样的操作,在绑定时要选择个动态端口,客户端的端口没必要再检查出来(因为没有人想连接上它)。
  • IP地址也能够自动分配,但是细节有些复杂。如果要做:

my_addr.sin_addr.s_addr=htonl(INADDR_ANY);
内核会用服务运行的机器上的IP来填充。

  • 注意,一个机器可能用多个IP。
  • 在使用Unix域socket时,用文件入口来配合进行bind()的绑定,示例:
struct  sockaddr_un addr;
…
sd=socket(PF_UNIX,SOCK_STREAM,0);
addr.sun_family=AF_UNIX;
strcpy(addr.sun_path,”/tmp/mysock”);
bind(sd,(struct  sockaddr*)&addr,sizeof(addr));
  • 成功时返回0。可能的错误有:

EBADF:无效的描述符。

  • 如果地址已经使用了,内核会报错,这是bind()遇到的常见错误。在服务进程能够运行多个实例时,这经常会发生,或者在终止后“太快”重启也会导致这类错误(也就是需要时间让内核释放端口)。
  • 要解决上面说的问题,要设置选项:
int yes=1;
setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes));
在socket创建后,bind前,调用上面代码,就可以解决问题。后面讲修改socket选项。

3.19.3 listen()函数

  • listen()函数只在服务器端调用。在bind()函数调用后,才调用它,它让socket进入一个被动状态,也就是说,内核应该接收指向这个socket的连接。
  • 函数原型:

#include <sys/socket.h>
int listen(int sd,int backlog);

  • 只有SOCK_STREAM或SOCK_SEQPACKET类型才能调用listen()函数。
  • 第一个参数是描述符。第二个参数backlog,指定了在收入队列里可以放连接的个数(收入队列由内核来管理,进程不可能同时和多个客户端握手,只能一下接收一个,如果同时来多个,则其它的会先放到收入队列里)。
  • 不同操作系统对于收入队列理解会不一样。有2个特殊的队列需要维护:
  • 完成连接队列,它包含已经握手成功的客户端的入口
  • 未完成连接队列,它包含处于在握手前待处理的客户端的入口
  • 在Linux下,backlog描述了完成连接队列的长度。未完成连接队列的长度可以通过检查/proc/sys/net/ipv4/tcp_max_syn_backlog这个文件获取到。(含意是,修改它也会影响socket的反应能力)
  • 如果CONFIG_SYS_COOKIES在内核选项里设置上了,在连接过多超负荷时socket会得到保护;客户端不可能检测出来服务器端的超负荷。这样,因为阻塞导致的拒绝服务被避免了。最大值不再起作用,包也不会被丢弃。参考man里对tcp的帮助。(含意,如果服务器上的协议参数配置的有问题,程序写的再好,也不一定出效果的。底层配置要认真关注)。
  • 成功返回0,可能的错误有
    EADDRINUSE:其它的socket已经监听了同样的端口。

3.19.4 accept()函数

  • 在调用过socket(),bind()和listen()函数后,服务器需要调用accept()来使用socket:

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sd,struct sockaddr*addr,socklen_t *addrlen);

  • 只有面向连接的socket才能用accept()函数,比如SOCK_STREAM,SOCK_RDM和SOCK_SEQPACKET。
  • 成功时,返回新的socket描述符(非负数的整数),这个描述符会用于后续的与客户端交互的输入和输出操作。
  • 第三个参数addrlen在输入过值后,指向了给定长度的变量,长度表示了第二个参数地址结构体的长度。函数调用后,它会包含填充的地址结构变量的实际的长度(以字节为单位),这个地址结构和初始值有所不同。
  • 除非socket使用了fcntl()或setsockopt()函数强制设置为了非阻塞模式,如果没有未决的连接出现在队列里,accept()会阻塞,直到有客户端请求来临。
  • 如果带多个描述符使用了select()或poll()函数,应该调用这些函数,且等待来临的连接。等到时刻到来,才调用accept()函数,建立全套连接。
  • 服务器需要尽量多地处理来自多个客户端的并发请求,这是经常遇到的压力。有几个方法可以设计这样的并发服务器,比如为每个请求新建进程或线程。后面会讨论这个。
  • 可能的错误有:
  • 在连接建立后,可用下面函数:

int getpeername(int sd,const struct sockaddr *rem_addr,socklen_t addrlen);
会获取对端socket的地址和端口数字。

3.19.5 Unix方式服务器

  • 下面例子在无限循环里等待客户端连接。
  • 当有客户端连接上时,它会从本地socket里读取一行./mysock,接着回传到标准输出上。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

#define MSG_LEN 1024

int main (void)
{
struct sockaddr_un uaddr;
int rc, sd, cd;
socklen_t alen;
char message[MSG_LEN];
uaddr.sun_family = AF_UNIX;
strcpy (uaddr.sun_path, "/tmp/mysock");
sd = socket (PF_UNIX, SOCK_STREAM, 0);
unlink ("/tmp/mysock");//保证这个文件入口是空闲的
bind (sd, (struct sockaddr *)&uaddr,
sizeof (uaddr));
listen (sd, 5);
for (;;) {
cd = accept (sd, NULL, &alen);
rc = read (cd, message, sizeof (message));
write (STDOUT_FILENO, message, rc);
close (cd);
}
close (sd);
exit (EXIT_SUCCESS);
}

3.19.6 互联网服务器

  • 下面示例是个简单的echo回传服务
  • 回传就是收到什么发什么
  • 无限循环来接收连接
  • 使用上章写好的echo客户端可以测试这个服务器,测试前需要把端口从官方的7,修改为7177。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#define MSG_LEN 1024
#define PORT_NUMBER 7177

int main (void)
{
struct sockaddr_in addr, con_addr;
int rc, sd, cd, yes = 1;
socklen_t alen;
char message[MSG_LEN];
//构造地址
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl (INADDR_ANY);//注意网络字节顺序处理
addr.sin_port = htons (PORT_NUMBER);
sd = socket (PF_INET, SOCK_STREAM, 0);
//设置端口使用可以重用
setsockopt (sd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (yes));
bind (sd, (struct sockaddr *)&addr, sizeof (addr));
listen (sd, 5);//listen的第二个参数一般设置为5
for (;;) {
printf ("\nAccepting input on port %d\n", PORT_NUMBER);
cd = accept (sd, (struct sockaddr *)&con_addr, &alen);
rc = read (cd, message, sizeof (message));
write (STDOUT_FILENO, message, rc);
write (cd, message, rc);
printf ("Received the end of input\n\n");
close (cd);
}
close (sd);
exit (EXIT_SUCCESS);
}
友情链接
版权所有 Copyright(c)2004-2024 锐英源软件
统一社会信用代码:91410105098562502G 豫ICP备08007559号 最佳分辨率 1440*900
地址:郑州市金水区文化路97号郑州大学北区院内南门附近