Linux TCP Socket程序分析

/************************

c语言编写的tcp socket通信的server端。

可以持续监听myprot指定的端口

打印端口接收到的字符流

头文件因为尖括号被转义,所以用了引号

************************/

#include "stdio.h"

#include "stdlib.h"
#include "errno.h"
#include "string.h"

#include "sys/types.h"

#include "netinet/in.h"
#include "sys/socket.h"
#include "sys/wait.h"

int main(int argc,char **argv)
{
    int sockfd, new_fd;
    struct sockaddr_in my_addr;
    struct sockaddr_in their_addr;
    unsigned int sin_size,myport,listnum;
    myport = 9785;  //绑定的端口号
    listnum = 10;
    /*************************************************
     Socket接口:是TCP/IP网络的API,Socket接口定义了许多的函数,可以
                 在此基础上开发Internet上的TCP/IP网络编程
    
     Create Socket: int socket(int domain, int type, int protoco);
    
     Argument Description:domain 指明所有协议族,通常是PF_INET(TCP/IPV4)
                          当然他也可以支持IPV6,和更多的网络协议,根据
                          具体的应用来选择
                          type 分SOCK_STREAM(TCP),SOCK_DGRAM(UDP),SOCK_RAW
                          (允许程序使用底层协议)
                          protolol 通常赋值“0”
   
    Return Value: Socket描述符是一个指向内部数据结构的指针,它指向描述符表
                    入口。调用Socket函数时,socket执行体将建立一个Socket,
                    实际上"建立一个Socket"意味着为一个Socket数据结构分配存
                    储空间。Socket执行体为你管理描述符表。
    **************************************************/
    if((sockfd = socket(PF_INET,SOCK_STREAM,0)) == -1 )
    {
        perror("socket is error/n;");
        exit(1);
    }
    my_addr.sin_family = PF_INET;           //指定协议族


    /***************************************************
      计算机数据存储有两种字节优先顺序:高位字节优先和低
      位字节优先。Internet上数据以高位字节优先顺序在网络
      上传输,所以对于在内部是以低位字节优先方式存储数据
      的机器,在Internet上传输数据时就需要进行转换,否则
      就会出现数据不一致。
      htonl():把32位值从主机字节序转换成网络字节序
      htons():把16位值从主机字节序转换成网络字节序
      ntohl():把32位值从网络字节序转换成主机字节序
      ntohs():把16位值从网络字节序转换成主机字节序
    ****************************************************/
    my_addr.sin_port = htons(myport);      //如果填入0,系统将随机选择一个端口
   
    my_addr.sin_addr.s_addr = INADDR_ANY;  //填入本机IP地址
    bzero(&(my_addr.sin_zero),0);

    /**************************************************
      int   bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
      Description:创建socket,并绑定该端口,申明已经使用,别人不会再占用该端口

      Argument Description: sockfd:是Socket系统调用返回的socket 描述符
                            my_addr:需要绑定在套接字上的地址,
                                     是类似于以下结构体的变量
                            struct sockaddr {
                                                sa_family_t sa_family;
                                                char        sa_data[14];
                                            }
     Return Value: 成功执行时,返回0。失败返回-1,errno被设置出错信息
    ***************************************************/
     if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr)) == -1)
    {
        perror("bind is error/n");
        exit(1);
    }

    /**************************************************
     int listen(int sockfd, int ListenSum)
     Description: Listen()函数使socket处于被动的监听模式,
                  并为该socket建立一个输入数据队列,将
                  到达的服务请求保存在此队列中,直到程
                  序处理它们。
     Argument Description:sockfd: 是Socket系统调用返回的socket 描述符
                          ListenSum: 指定在请求队列中允许的最大请求数
     Return Value: 成功执行时,返回0。失败返回-1,errno被设置出错信息
    ***************************************************/
    if(listen(sockfd,listnum) == -1)
    {
        perror("listen is error/n");
        exit(1);
    }
        printf("start to listen/n");
    while(1)
    {
        sin_size = sizeof(struct sockaddr_in);

        /***********************************************************
         int accept(int sockfd, void *addr, int *addrlen)
         Description:accept()函数让服务器接收客户的连接请求。
                     在建立好输入队列后,服务器就调用accept
                     函数,然后睡眠并等待客户的连接请求。
       Argument Description:sockfd: 是Socket系统调用返回的socket 描述符
                            addr  : 通常是一个指向sockaddr_in变量的指针,
                                    该变量用来存放提出连接请求服务的主
                                    机的信息(某台主机从某个端口发出该请求)
                            addrten: 通常为一个指向值为
                                     sizeof(struct sockaddr_in)的整型指针变量
        Return Value: 失败返回-1,errno被设置出错信息
                      成功返回  当accept函数监视的socket收到连接请求时,
                                 socket执行体将建立一个新的 socket,执
                                 行体将这个新socket和请求连接进程的地址
                                 联系起来,收到服务请求的初始socket仍可
                                 以继续在以前的 socket上监听,同时可以在
                                 新的socket描述符上进行数据传输操作
        ***************************************************************/
        if((new_fd = accept(sockfd,(struct sockaddr *)&their_addr,&sin_size)) == -1)
        {
            perror("accept is error/n");
            continue;
       
        }
        printf("server:got connection from %s/n",inet_ntoa(their_addr.sin_addr));


                char *p;
                char sock_buf[1024];
                bzero(sock_buf, 1024);
                p = sock_buf;
                int rval=0;

    /**************************************************************************
int recv(SOCKET s, char FAR *buf, int len, int flags );
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。

该函数的第一个参数指定接收端套接字描述符;

第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

第三个参数指明buf的长度;

第四个参数一般置0。

这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,如果协 议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数据或者数据被协议成功发送完毕 后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到协议把数据接收完毕。当协议把 数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次 recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy 的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
    ***************************************************************************/

                if ((rval = recv(new_fd, p, 1024, 0)) < 0)
                {
                        printf("recv errror/n");
                }
                else
                {
                        printf("recv %s/n",p);
                }
    /**************************************************************************
      fork一个子进程对此次连接同父进程并行运行    ***************************************************************************/
    if(!fork())
    {
       
        /***********************************************************************
         int send(int sockfd, const void *msg, int len, int flags)
         Description:通过子进程发送信息给客户端
         Argument Description: sockfd: 是你想用来传输数据的socket描述符
                               msg   : 是一个指向要发送数据的指针
                               Len   : 是以字节为单位的数据的长度
                               flags : 一般情况下置为0(这个参数涉及到阻塞和非阻塞问题)
        ************************************************************************/
        if(send(new_fd,"hello,HuHan/n",14,0) == -1)
        {
            perror("send is error/n");
            close(new_fd);
            exit(0);
        }
    }

    /******************************************************************************
     当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在
     该socket上的任何数据操作

     如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。

   int shutdown(int sockfd,int how);

  Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式:
    ·0-------不允许继续接收数据
    ·1-------不允许继续发送数据
    ·2-------不允许继续发送和接收数据
    shutdown在操作成功时返回0,在出现错误时返回-1并置相应errno。
    *******************************************************************************/
    close(new_fd);
   
    /****************************************************************************
     pid_t waitpid(pid_t pid, int *stat_loc, int option)
     pid参数指定需要等待的子进程的PID,如果是-1,waitpid将返回任一子进程的信息,与
     wati()一样,如果stat_loc不是空指针,waitpid将把状态信息写到所指向的位置。option
     参数允许我们改变waitpid的行为,其中最有用的一个选项是WNOHANG,他的作用是防止
     waitpid()调用将调用者执行挂起,可以用这个选项来查找是否有子进程已经结束,如果没有
     将继续执行。
    *****************************************************************************/
    waitpid(-1,NULL,WNOHANG);
    }
}

 

客户端程序:

#i nclude <stdlib.h>

#i nclude <stdio.h>

#i nclude <errno.h>

#i nclude <string.h>

#i nclude <netdb.h>

#i nclude <sys/types.h>

#i nclude <netinet/in.h>

#i nclude <sys/socket.h>

int main(int argc, char *argv[])

{

int sockfd;

char buffer[1024];

struct sockaddr_in server_addr;

struct hostent *host;

int portnumber,nbytes;

if(argc!=3)

{

printf("Usage:%s hostname portnumber/a/n",argv[0]);

exit(1);

}

/*gethostbyname 可以通过主机名称得到主机的 IP 地址 */

if((host=gethostbyname(argv[1]))==NULL)

{

printf("Gethostname error/n");

exit(1);

}

/*portnumber 为端口号 */

if((portnumber=atoi(argv[2]))<0)

{

printf("Usage:%s hostname portnumber/a/n",argv[0]);

exit(1);

}

/* 客户程序开始建立 sockfd 描述符 */

if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)

{

printf("Socket Error:%s/a/n",strerror(errno));

exit(1);

}

/* 客户程序填充服务端的资料 */

bzero(&server_addr,sizeof(server_addr));

server_addr.sin_family=AF_INET;

/* 主机字节序转换为网络字节序 */

server_addr.sin_port=htons(portnumber);

server_addr.sin_addr=*((struct in_addr *)host->h_addr);

/* 客户程序发起连接请求 */

if(connect(sockfd,(struct sockaddr *)(&server_addr),

sizeof(struct sockaddr))==-1)

{

printf("Connect Error:%s/a/n",strerror(errno));

exit(1);

}

/* 连接成功了 */

if((nbytes=read(sockfd,buffer,1024))==-1)

{

printf("Read Error:%s/n",strerror(errno));

exit(1);

}

buffer[nbytes]='/0';

printf("I have received:%s/n",buffer);

/* 结束通讯 */

close(sockfd);

exit(0);

}

你可能感兴趣的:(linux,socket,tcp,struct,server,网络)