您的位置:首页 > 理论基础 > 计算机网络

网络基础篇——socket编程之TCP服务器

2016-07-26 23:59 357 查看
1.socket编程

socket有很多意思,在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标示网络通讯中的一个进程,“IP地址+端口号”就称为socket。

在TCP协议中,建立连接的两个进程各有一个socket来标示,那么这两个socket组成的socketpair就唯一标示一个连接.socket本身有插座的意思,因此用来描述网络连接中的一对关系。

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。如果发送主机是小端机,则在发送前需要做字节序的转换。

字节转换函数:



这些函数名很好记,h标示host(主机),n标示network(网络),l表示32位长整数,s表示16位短整数。

struct sockaddr
{
unsigned short  sa_family;   //地址族, 一般为AF_INET
char                  sa_data[14];   //14字节的协议地址
}


socket地址的数据类型



IPv4地址用用sockaddr_in结构体来表示,IPv6用sockaddr_in6结构体表示。UNIX Domain Socket的地址用sockaddr_un结构体表示。

各 种socket地址结构体的开头都是相同的,前16位表⽰示整个结构体的长度(并不是所有UNIX的实现 都有长度字段,如Linux就没有),后16位表⽰示地址类型。IPv4、IPv6和UNIXDomain Socket的地 址类型分别定义为常数AF_INET、AF_INET6 AF_UNIX。这样,只要取得某种sockaddr结构体的 ⾸首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的 内容。因此,socket API可以接受各种类型的sockaddr结构体指针做参数。

我们研究下基于IPv4的网络编程

sockaddr_in结构体内容



TCP服务器需要的函数如下:

socket函数



[b]_______________[/b]返回值:非负描述符 – 成功,-1 - 出错

其中:

family指明了协议族/域,通常AF_INET、AF_INET6、AF_LOCAL等;

type是套接口类型,主要SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;

protocol一般取为0。成功时,返回一个小的非负整数值,与文件描述符类似。

socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个⽂文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。对 于IPv4,family参数指定为AF_INET。对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表⽰示面向数据报的传输协议。protocol参数的介绍从略,指定为0即可。

listen



参数sockfd

被listen函数作用的套接字,sockfd之前由socket函数返回。在被socket函数返回的套接字fd之时,它是一个主动连接的套接字,也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。

参数backlog

这个参数涉及到一些网络的细节。在进程正理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。

毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。

当调用listen之后,服务器进程就可以调用accept来接受一个外来的请求。

bzero

 原型:extern void bzero(void *s, int n);  

功能:置字节字符串s的前n个字节为零且包括‘\0’。  

说明:bzero无返回值,并且使用strings.h头文件

绑定端口和IP地址bind

int  bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen)


返回值——成功返回0,失败返回-1

当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数可以将一组固定的地址绑定到sockfd上。

其中:

sockfd是socket函数返回的描述符;

myaddr指定了想要绑定的IP和端口号,均要使用网络字节序-即大端模式;

addrlen是前面struct sockaddr(与sockaddr_in等价)的长度。

字符串转in_addr函数和in_addr转字符串的函数



accept

头文件:

#include

ssize_t write(int fd,const void* buf,size_t nbytes);


write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量. 在网络程序中,当我们向套接字文件描述符写时有两可能.

1)write的返回值大于0,表示写了部分或者是全部的数据. 这样我们用一个while循环来不停的写入,但是循环过程中的buf参数和nbyte参数得由我们来更新。也就是说,网络写函数是不负责将全部数据写完之后在返回的。

2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理.

如果错误为EINTR表示在写的时候出现了中断错误.

如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).

读函数read原型

ssize_t read(int fd,void* buf,size_t nbyte)

read函数是负责从fd中读取内容.当读成功 时,read返回实际所读的字节数,如果返回的值是0 表示已经读到文件的结束了,小于0表示出现了错误.如果错误为EINTR说明读是由中断引起 的, 如果是ECONNREST表示网络连接出了问题. 和上面一样,我们也写一个自己的读函数.

服务器:







客户端:





运行结果:



在client发起连接请求时,服务器开辟线程来实现可以支持多客户端访问。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息