socket--多进程,多线程服务器

一:概念:
我们知道IP地址是标志网络中不用主机的IP地址,而端口号就是同一台主机上标志不同进程的地址,IP地址和端口号标志网络中的唯一地址.(又称socket)
在TCP协议中,建⽴立连接的两个进程各⾃自有⼀一个socket来标识,那么这两个组成 的socket就唯⼀一标识⼀一个连接。socket本⾝身有“插座”的意思,因此⽤用来描述网络连接的一一 对应关系.
二:通信中的大端小端的问题如何解决?
我们知道每台机器的大小端都可能不同.网路中也一样,当两台主机进行通信的时候那么如何解决大小端的问题呢?小端:数据的低位在低地址,高位在高地址;大端:数据的低位在高地址,高位在低地址.网络数据流先发出的是低地址,后发出的是高地址.TCP/IP规定,网络数据流采用大端字节序,即就是低位在高地址.如果接受主机是小端序列,发送主机是大端序列那么如何解决它们之间的转化呢?

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换
这里写图片描述
如果主机是小端字节序,这些函数将参数左相应的大小端转换后返回,如果是大端字节序,这些函数不做转换,将参数原封不动地返回.
三:单进程的套接字通信
服务器:
1:调用socket,请求系统分配文件描述符
这里写图片描述
参数类型:
type的取值
这里写图片描述
2:调用bing,绑定本机的信息,包括IP地址和端口号
这里写图片描述
3:调用listen,监听
这里写图片描述
函数功能:使上述socket返回的文件描述符sockfd进入监听状态;backlog:连接队列的长度,当一个请求次数超过长度时,就报错
4:调用accept,接收连接请求的socket
5:read读取socket中的数据
6:通信完成后,调用close关闭套接字

客户端
1:调用socket,请求系统分配文件描述符
2:调用connect,连接服务器
这里写图片描述
3:调用read函数标准输入中读取数据,放到自定义缓冲区buf中,然后将buf中的数据写到套接字中
4;通信完成后,调用close关闭套接字.
验证:
Makafile文件

.PHONY:all
all:tcp_server tcp_client
tcp:tcp_server tcp_client
        gcc- o $@ $^
tcp_client:tcp_client.c
        gcc -o $@ $^
.PHONY:clean
clean:
        rm -f tcp_server tcp_client

tcp_server文件:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
int StartUp(int port,const char*ip)
{
    int Listen =socket(AF_INET,SOCK_STREAM,0);
    if(Listen<0)
    {
        perror("Listen");
        exit(1);
     }
    struct sockaddr_in local;
    local.sin_family =AF_INET;
    local.sin_port =htons(port);
    local.sin_addr.s_addr =inet_addr(ip);
    if(bind(Listen,(struct sockaddr*)&local,sizeof(local))<0)
   {
        perror("bind");
        exit(2);
    }
    if(listen(Listen,5)<0)
    {
        perror("listen");
        exit(3);
    }
    return Listen;
}
int main(int argc,const char*argv[])
{
    if(argc !=3)
    {
        printf("input error\n");
        return 1;
    }
    int len;
    int Listen =StartUp(atoi(argv[2]),argv[1]);
    struct sockaddr_in client;
    while(1)
    {
        int sock = accept(Listen,(struct sockaddr*)&client,&len);
        if(sock<0)
        {
            perror("accept");
            continue;
        }
        printf("get a client ,ip is %s,part is%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
        char buf[1024];
        while(1)
        {
            ssize_t s =read(sock,buf,sizeof(buf)-1);//服务器读数据
            if(s>0)
            {
                buf[s] =0;
                printf("client #%s\n",buf);
            }
            else{
                //数据读完了.客户端不发送数据
                printf("client is quit!\n");
            }
        }
        close(sock);
    }
    return 0;
}

tcp_client文件:

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<arpa/inet.h>
int main(int argc,const char*argv[])
{
 int sock =socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
     perror("socket");
        return 1;
    }
    struct sockaddr_in server;
    server.sin_family =AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr =inet_addr(argv[1]);
    if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0)
       {
           perror("connect");
           return 2;
       }
       char buf[1024];
       while(1)
       {
           printf("send#");
           fflush(stdout);
           //从标准输入读数据,读到buf中,然后从Buf写到管道中
           ssize_t s =read(0,buf,sizeof(buf)-1);
           if(s<0)
           {
               perror("read");
               return 3;
           }
           buf[s-1] =0;
           write(sock,buf,s);
       }
       close(sock);
       return 0;
}

结果:
服务器
这里写图片描述
这里写图片描述
客户端:
这里写图片描述
一种现象:

这里写图片描述
这是什么原因呢?服务器终止程序,服务器就是主动发起断开连接请求的一方,根据TCP的3次握手4次挥手协议,主动发起连接断开请求的一方,最后必须等待2MSL的时间确认客户端是否收到自己的确认信息。这里,我们立即运行server的时候,server还是在TIME_WAIT状态,所以bind的时候就会出现地址已经被占用。
解决办法:socket之后,bind之前,加语句
int opt = 1;
setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
就是为了处理这个问题。

四:多进程socket网络通信:
在实际应用当中,可能有这种情况有多个客户端都给服务器发送数据,那么前面那种单进程的肯定有问题,这时我们需要多进程socket通信,就可以实现:
实现方法:服务器端可以创建多个子进程去处理客户端发来的数据,当每次收到一个新的客户端的连接请求的时候,我们就会fork()出一个子进程,父进程用于等待子进程,子进程用于执行,读客户端法的数据的操作,终止子进程,儿子进程被它的父进程回收,此时的孙子进程就是一个孤儿进程,被1号进程(操作系统回收),这样做的目的就是,不要让儿子进程等待孙子进程太久而消耗太多的系统资源.

这里写图片描述
注意:父进程关闭套接字,子进程关闭监听套接字这时因为,父进程是来监听的,不需要通信子进程是读取信息的,不需要监听.
验证:

Makefile文件

.PHONY:all
all:server client
server:server.c
gcc -o @ ^
client:client.c
gcc -o @ ^
.PHONY:
rm -f server client

.server文件

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int Startup(int port,const char*ip)
{
    int ListenSock =socket(AF_INET,SOCK_STREAM,0);
    if(ListenSock<0)
    {
        perror("socket");
        exit(1);
    }
    int opt =1;
    setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    struct sockaddr_in local;
    local.sin_family =AF_INET;
    local.sin_port =htons(port);
    local.sin_addr.s_addr =inet_addr(ip);
    if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("nind");
        exit(2);
    }
    if(listen(ListenSock,5)<0)
    {
        perror("listen");
        exit(3);
    }
    return ListenSock;
}
int main(int argc,const char*argv[])
{
    if(argc!=3)
    {
        printf("input error\n");
        return 1;
    }
    int len;
    int ListenSocket =Startup(atoi(argv[2]),argv[1]);
    struct sockaddr_in client;
    while(1)
    {
        int sock =accept(ListenSocket,(struct sockaddr*)&client,&len);//获取客户端的信息
        if(sock<0){
            perror("accept");
            continue;//获取失败继续accept
        }
        printf("get a client ,ip is %s,port is %d\n",inet_ntoa(client.sin_addr),\
              ntohs(client.sin_port));
        int id =fork();
        if(id>0)
        {
            close(sock);
            while(waitpid(-1,NULL,WNOHANG)>0);
            continue;
        }
        else{
            close(ListenSocket);
            if(fork()>0)
            {
                exit(0);
            }
                char buf[1024];
                while(1)
                {
                  ssize_t s = read(sock,buf,sizeof(buf)-1);
                    if(s>0)
                    {
                        buf[s] =0;
                        printf("client # %s\n",buf);
                    }
                    else{
                        printf("client is quit\n");
                        break;
                    }
                }
                close(sock);
                break;
            }
        }
        return 0;
    }

.clent文件

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<arpa/inet.h>
int main(int argc,const char*argv[])
{
    int sock =socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        return 1;
    }
    struct sockaddr_in server;
    server.sin_family =AF_INET;
    server.sin_port = htons(atoi(argv[2]));
     server.sin_addr.s_addr = inet_addr(argv[1]);
    if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0)
     {
                perror("connect");
                return 2;

      }
    char buf[1024];
     while(1)
    {
                printf("send# ");
                fflush(stdout);
                //从标准输入读数据,读到buf中,然后从buf写到管道
           ssize_t s = read(0,buf,sizeof(buf)-1);
                    if(s < 0)
                    {          
                    perror("read");
                       break;
                    }  
        buf[s-1] = 0;
        write(sock,buf,s);

    }
        close(sock);
        return 0;
}

当然这个也可以跟上一种方法一样用本地环回测试但最好的方式还是连接多个主机都向向server主机发数据,观察现象.
五:多线程的socket网络编程:
再前面我们知道线程是进程内部的执行分支,是在进程的地址空间中运行,而进程的缺点时比较占用系统的资源,当用户过多时,经受不住太多用户的访问,所以我们的另外一种方法就是采用线程实现通信.
方法:
主线程创建出一个新线程,新线程的执行函数是读取信息,类似上边的多进程间的通信,我们可以将新的线程进行分离detch(),分离之后的线程就不需要主线程去等待,而是由操作系统区回收(这里最好不要用join线程等待,减小的等的时间)
验证
.Makefile文件:
.PHONY:all
all:server client
server:server.c
gcc -o @ ^ -lpthread
client:client.c
gcc -o @ ^ -lpthread
.PHONY:
rm -f server client
.server文件

#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
int StartUp(int port,const char* ip)
{
        int sock = socket(AF_INET,SOCK_STREAM,0);
        if(sock < 0)
    {
                perror("socket");
                exit(2);

    }
        int opt = 1;
        setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(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* thread_hander(void* arg)
{
        int sock = *((int*)arg);
        char buf[1024];
        while(1)
    {
                ssize_t _s = read(sock,buf,sizeof(buf)-1);
                if(_s > 0)
        {
                        buf[_s-1] = 0;
                        printf("client say#%s\n",buf);
                        if(write(sock,buf,sizeof(buf)-1)<0)
            {
                                break;

            }

        }
                else if(_s == 0)
        {
                        printf("client is quit!\n");
                        break;

        }
                else
        {
                        perror("read");
                        break;

        }

    }
        close(sock);
}
int main(int argc,const char* argv[])
{
        if(argc != 3)
    {
                printf("input  error\n");
                return 1;

    }
        int listenSock = StartUp(atoi(argv[2]),argv[1]);
        struct sockaddr_in client;
        int len = 0;
        while(1)
    {
                int sock = accept(listenSock,(struct sockaddr*)&client,&len);
                if(sock < 0)
        {
                        perror("accept");
                        return 5;

        }
                printf("get a client!ip is %s,port is %d\n",inet_ntoa(client.sin_addr),\
                                      ntohs(client.sin_port));
                pthread_t tid;
                int ret = pthread_create(&tid,NULL,thread_hander,&sock);
                if(ret < 0)
        {
                        perror("pthread_create");
                        return 6;

        }
                pthread_detach(tid);

    }
        return 0;
}

.client文件

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main(int argc, const char* argv[])
{
        if(argc != 3)
    {
                printf("input error\n");
                return 1;

    }
        int sock = socket(AF_INET,SOCK_STREAM,0);
        if(sock < 0)
    {
                perror("socket");
                return 2;

    }
        struct sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(atoi(argv[2]));
        server.sin_addr.s_addr = inet_addr(argv[1]);
        int ret = connect(sock,(struct sockaddr*)&server,sizeof(server));
        if(connect < 0)
    {
                perror("connect");
                return 3;

    }
        char buf[1024];
        while(1)
    {
                printf("send#");
                fflush(stdout);
                ssize_t _s = read(0,buf,sizeof(buf)-1);
                if(_s > 0)
        {
                        buf[_s - 1] = 0;
                        if(write(sock,buf,sizeof(buf)-1) < 0)
            {
                                break;

            }
                        ssize_t s = read(sock,buf,sizeof(buf)-1);
                        if(s > 0)
            {
                                buf[s] = 0;
                                printf("server echo#%s\n",buf);

            }

        }
                else
        {
                        perror("read");
                        return 4;

        }

    }
    return 0;
}

本文参考:http://blog.csdn.net/peiyao456/article/details/61934721

  • 6
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值