TCP/IP服务器

来源:互联网 发布:mac自带画图工具 编辑:程序博客网 时间:2024/06/08 03:44

套接字

  TCP/IP服务器的网络基础编程是利用网络套接字进行通信的,所以应该先明确网络套接字时什么东西。
  首先,IP地址标识了网络中唯一的一台主机,TCP/UDP标识了一台主机中唯一的一个进程,所以IP地址+TCP/UDP端口号就标识了网络中唯一一个进程,我们称之为套接字。
  如果网络中服务器端和客户端各有一个套接字进行标记各自唯一的一个进程,那么组成这个socket pair的一组套接字就可以实现通信。
 

网络字节序

  因为各个版本的计算机上的大小端不一致,所以每个版本的计算机在解读地址空间里面存储的数据时,只会按本计算机遵守的方式读取或写入,但是这就在广域网、Internet网中无法统一。但是若是只按一种方式(即大端或小端)进行数据的上传、传输、下载的话,那么在现在的网络环境中,几乎一半的计算机都需要更改自己操作系统底层运行的逻辑,成本太大了。
  但是人的智慧时无穷的,经过商议,规定:无论本地计算机是大端还是小端,在上传到网络上时,都需要转换成大端再上传,然后从网络上读取数据后,根据本地计算机的情况进行转化。更好的是,系统已经为我们提供了一个转换的借口,调用此函数,可以自动检测本地服务的大小端,然后判断是否需要转化,从网络端下载数据时,同样调用此函数的另一个版本,判断是否需要转换,需要即转换。
  

       #include <arpa/inet.h>       uint32_t htonl(uint32_t hostlong);//本地转网络       uint16_t htons(uint16_t hostshort);       uint32_t ntohl(uint32_t netlong);//网络转本地       uint16_t ntohs(uint16_t netshort);

网络基础通信步骤

创建套接字

  第一步,各端使用socket函数创建各端的套接字。
  说到套接字,其实也就是文件描述符,因为在Linux下,“一切皆文件”,理所当然的,通信的工具也相当于一个文件,所以创建的套接字也就是文件描述符了,检测的方法也很简单,在使用socket前,不要创建任何文件,然后socket创建套接字成功后,输出此套接字,可以发现,是3。
  因为在一个进程中,没有其他操作的话,这个进程PCB中文件描述符表的前三个文件描述符时一开始就被定义的。
  0———标准输入;
  1———标准输出;
  2———标准错误输出。
  而操作系统分配新的文件描述符是从文件描述符表中提取一个最小的、没有使用的文件描述符。所以没有其他操作的话,在进程中分配的第一个文件描述符永远是3,下一个是4,接下来是5,6,7…
  

       #include <sys/types.h>          /* See NOTES */       #include <sys/socket.h>       int socket(int domain, int type, int protocol);

  damain参数的含义是此套接字遵守的地址类型,采用宏定义,定义了各个类型的协议
  

       Name                Purpose                          Man page       AF_UNIX, AF_LOCAL   Local communication              unix(7)       AF_INET             IPv4 Internet protocols          ip(7)       AF_INET6            IPv6 Internet protocols          ipv6(7)       AF_IPX              IPX - Novell protocols       AF_NETLINK          Kernel user interface device     netlink(7)       AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)       AF_AX25             Amateur radio AX.25 protocol       AF_ATMPVC           Access to raw ATM PVCs       AF_APPLETALK        Appletalk                        ddp(7)       AF_PACKET           Low level packet interface       packet(7)

  参数type是表示此套接字通信时的数据类型,同样可以在Linux终端使用man手册查询“man socket”。
  protocol当然是该协议的类型。
  然后使用服务器端使用bind函数绑定套接字。

绑定套接字

       #include <sys/types.h>          /* See NOTES */       #include <sys/socket.h>       int bind(int sockfd, const struct sockaddr *addr,                socklen_t addrlen);

  sockfd是之前socket创建的套接字,第二个参数的结构体时如下结构:
  

struct sockaddr_in{    short int                      sin_family ;    unsigned short int             sin_port;    struct in_addr                 sin_addr;    unsigned char                  sin_sero[8];}struct in_addr{    in_addr_t           s_addr;}

  sin_family含义同socket中family的含义,port端口号,此时设置端口号时要对端口号使用htons进行转换,sin_addr是ip地址。sin_zero[8]暂时忽略。
  因为每个通信的类型不尽相同,所以在定义结构体时,我的计算机以struct sockadd_in定义,但是在传入网络时需要统一传入,使用struct sockadd*的类型进行传入,所以要进行强制类型转换。
  sockaddr、sockaddr_in、sockaddr_un各自模型如下:
  这里写图片描述

监听套接字

  在服务器端还要对该套接字进行监听,以便了解下一次数据过来的时机
  

       #include <sys/types.h>          /* See NOTES */       #include <sys/socket.h>       int listen(int sockfd, int backlog);

  backlog时底层连接的数目。这个数目不宜过大,我一般设置为5–10。


  此时就可以使用accept函数对其进行接收了,accept函数以阻塞的方式一直等待socket函数创建的套接字的访问。
  

       #include <sys/types.h>          /* See NOTES */       #include <sys/socket.h>       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);       #define _GNU_SOURCE       #include <sys/socket.h>       int accept4(int sockfd, struct sockaddr *addr,                   socklen_t *addrlen, int flags);

  一般使用第一个版本,但注意在传入结构体的指针时,同bind函数一样,需要进行强制类型转换。
  然后根据http://blog.csdn.net/sinat_36118270/article/details/73610518
  本篇中的TCP三次握手建立连接。
  代码实现:
  

代码实现

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <string.h>static void usage(const char* str){    printf("Please Enter# ");    printf("%s [local-ip]--[local-port]\n",str);}int startup(const char* _ip,const char* _port){    int sock=socket(AF_INET,SOCK_STREAM,0);    if(sock<0)    {        perror("socket");        exit(2);    }    struct sockaddr_in local;    local.sin_family=AF_INET;    local.sin_port=htons(atoi(_port));    local.sin_addr.s_addr=inet_addr(_ip);    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)    {        perror("bind");        exit(3);    }    if(listen(sock,5)<0)    {        perror("listen");        exit(4);    }    return sock;}int main(int argc,char *argv[])//从命令行接收IP地址和端口号{    if(argc!=3)    {           usage(argv[0]);        exit(1);    }    int listen_sock = startup(argv[1],argv[2]);创建套接字,并绑定、监听    while(1)    {        struct sockaddr_in client;        socklen_t len=sizeof(client);        int new_fd=accept(listen_sock,(struct sockaddr*)&client,&len);//阻塞等待连接        if(new_fd<0)        {            perror("accept");            exit(5);        }        //已连接        while(1)        {            char buff[1024];            ssize_t s=read(new_fd,buff,sizeof(buff)-1);            if(s>0)            {                buff[s]=0;                printf("[client%s - %d# %s",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buff);//show client words                write(new_fd,buff,strlen(buff));//send words what client said to client,back show            }            else            {                close(new_fd);            }        }    }}
//client.c#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <string.h>static void usage(const char* str){    printf("Please Enter# ");    printf("%s [local-ip]--[local-port]\n",str);}int startup(const char* _ip,const char* _port){    int sock=socket(AF_INET,SOCK_STREAM,0);//只创建套接字    if(sock<0)    {        perror("socket");        exit(2);    }    return sock;}int main(int argc,char *argv[]){    if(argc!=3)    {           usage(argv[0]);        exit(1);    }    int sock = startup(argv[1],argv[2]);    struct sockaddr_in server;    server.sin_family=AF_INET;    server.sin_port=htons(atoi(argv[2]));    inet_pton(AF_INET,argv[1],&server.sin_addr.s_addr);    socklen_t len=sizeof(server);    if(connect(sock,(struct sockaddr*)&server,len)<0)    {        perror("connect");        exit(5);    }  //连接成功    char buff[1024];    while(1)    {        printf("Please Enter# ");        fflush(stdout);        ssize_t s=read(0,buff,sizeof(buff)-1);        if(s>0)        {            buff[s]=0;            write(sock,buff,strlen(buff));            ssize_t _s=read(sock,buff,sizeof(buff)-1);            if(_s>0)            {                printf("--------># %s",buff);            }        }    }}

多线程版本

//server.c#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <string.h>static void usage(const char* str){    printf("Please Enter# ");    printf("%s [local-ip]--[local-port]\n",str);}int startup(const char* _ip,const char* _port){    int sock=socket(AF_INET,SOCK_STREAM,0);    if(sock<0)    {        perror("socket");        exit(2);    }    struct sockaddr_in local;    local.sin_family=AF_INET;    local.sin_port=htons(atoi(_port));    local.sin_addr.s_addr=inet_addr(_ip);    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)    {        perror("bind");        exit(3);    }    if(listen(sock,5)<0)    {        perror("listen");        exit(4);    }    return sock;}void* fun(void* fd){    int new_fd=(int)fd;    while(1)    {        char buff[1024];        ssize_t s=read(new_fd,buff,sizeof(buff)-1);        if(s>0)        {            buff[s]=0;            printf("%s",buff);//show client words            write(new_fd,buff,strlen(buff));//send words what client said to client,back show        }        else        {            close(new_fd);            pthread_exit(9);        }    }}int main(int argc,char *argv[]){    if(argc!=3)    {           usage(argv[0]);        exit(1);    }    int listen_sock = startup(argv[1],argv[2]);    while(1)    {        struct sockaddr_in client;        socklen_t len=sizeof(client);        int new_fd=accept(listen_sock,(struct sockaddr*)&client,&len);        if(new_fd<0)        {            perror("accept");            exit(5);        }            printf("[client%s - %d# ",inet_ntoa(client.sin_addr),ntohs(client.sin_port));//show client words        pthread_t temp;        pthread_create(&temp,NULL,fun,(void*)new_fd);        pthread_detach(temp);}
//client.c#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <string.h>static void usage(const char* str){    printf("Please Enter# ");    printf("%s [local-ip]--[local-port]\n",str);}int startup(const char* _ip,const char* _port){    int sock=socket(AF_INET,SOCK_STREAM,0);    if(sock<0)    {        perror("socket");        exit(2);    }    return sock;}int main(int argc,char *argv[]){    if(argc!=3)    {           usage(argv[0]);        exit(1);    }    int sock = startup(argv[1],argv[2]);    struct sockaddr_in server;    server.sin_family=AF_INET;    server.sin_port=htons(atoi(argv[2]));    inet_pton(AF_INET,argv[1],&server.sin_addr.s_addr);    socklen_t len=sizeof(server);    if(connect(sock,(struct sockaddr*)&server,len)<0)    {        perror("connect");        exit(5);    }    char buff[1024];    while(1)    {        printf("Please Enter# ");        fflush(stdout);        ssize_t s=read(0,buff,sizeof(buff)-1);        if(s>0)        {            buff[s]=0;            write(sock,buff,strlen(buff));            ssize_t _s=read(sock,buff,sizeof(buff)-1);            if(_s>0)            {                printf("--------># %s",buff);            }        }    }}

关于IP地址的转换:可参考另一篇文章:http://blog.csdn.net/sinat_36118270/article/details/73521771


client端不进行绑定监听的原因

  聪明得你可能已经发现,client端并没有进行绑定和监听。这时因为在服务器端,要对每一个用户进行唯一的服务,因为每一个用户所需的服务都不相同,所以要进行绑定,使用户和一个套接字唯一对应,而监听就是需要监听这个用户需要的服务。但作为client端,并不需要和服务器进行绑定,因为我可能这个对这个服务器进行访问,下次对另一个服务器进行访问;并且对一个服务器的连续访问时间间隔大,那如果我绑定了唯一的一个套接字(文件描述符)。一定程度的访问后,我就没有新的资源(文件描述符)对新的服务器进行访问了。