linux网络编程-socket

Posted luzhouxiaoshuai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux网络编程-socket相关的知识,希望对你有一定的参考价值。

当客户端调用close函数的时候,服务器的read函数读到的数据是0读到文件结束通知,表示对端关闭了tcp连接

我们现实实现下面的功能:

 

 1、tcp客户端从标准的输入流中得到输入数据发送到服务器,服务器收到数据之后,不做任何改变,将书法返回给客户端,客户端收到服务器的数据之后,在标准输出流中输出

 

上面代码中PF_INET和AF_INET都是一样的都是代码tcp的协议族

tcp协议对应的流式套接字,所以写成sock_STREAM

第三个参数可以写成IPPPOTO_TCP或者0都是可以的

第四个结构体中服务器绑定的地址可以使用inet_addr函数将本机的点分十进制的ip地址转换成32位的网络地址

也可以使用htonl函数将本机的任何地址转换成32位的函数,其中INADDR_ANY,表示本机的任何地址都可以

对于端口也必须是网络字节序,所以需要使用htons将本机端口转换成16位的无符号网络字节端口

Read函数

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

    Read函数是负责从fd中读取内容,当读取成功时,read返回实际读取到的字节数,如果返回值是0,表示已经读取到文件的结束了,小于0表示是读取错误。

    如果错误是EINTR表示在写的时候出现了中断错误,如果是EPIPE表示网络连接出现了问题。

 

Write函数

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

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

我们来看下服务器的代码:





#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> /* *定义一个宏,输出错误信息并且退出 */ #define ERR_EXIT(m) \\ do \\ {\\ perror(m);\\ exit(EXIT_FAILURE);\\ }while(0) int main(int argc, char *argv[]) { int serv_sock; struct sockaddr_in serv_addr; serv_sock = socket(AF_INET, SOCK_STREAM, 0); if (serv_sock == -1) { ERR_EXIT("socket创建失败"); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(9999); if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<0){ ERR_EXIT("客户端connect失败"); } char revbuf[1024]; char sendbuf[1024]; memset(revbuf,0,sizeof(revbuf)); memset(sendbuf,0,sizeof(revbuf)); while((fgets(sendbuf,sizeof(sendbuf),stdin)!= NULL)){ write(serv_sock,sendbuf,strlen(sendbuf)); read(serv_sock,revbuf,sizeof(revbuf)); fputs(revbuf,stdout); //读到多少数据就给客户端返回多少字节的数据 memset(sendbuf,0,sizeof(revbuf)); memset(revbuf,0,sizeof(revbuf)); } close(serv_sock); return 0; }

 服务器代码:

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

/*
*定义一个宏,输出错误信息并且退出 
*/
#define ERR_EXIT(m) \\
do \\
{\\
perror(m);\\
exit(EXIT_FAILURE);\\
}while(0)

int main(int argc, char *argv[])
{
    int serv_sock;
    int clnt_sock;
    
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;
    
     
    
    serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
    ERR_EXIT("socket创建失败");
    }
    
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(9999);
    
    if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){
    ERR_EXIT("bind失败");
    }
    //SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接 
    if (listen(serv_sock, SOMAXCONN) == -1){
    ERR_EXIT("listen失败");    
    }
    clnt_addr_size = sizeof(clnt_addr);
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    if (clnt_sock == -1){
    ERR_EXIT("accept失败");    
    }
    
    char revbuf[1024];
    while(1){
    memset(revbuf,0,sizeof(revbuf));
    int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度 
    if(len == 0){ //说明客户端终止了数据的发送 
        break;
    }
    fputs(revbuf,stdout);
    //读到多少数据就给客户端返回多少字节的数据 
    write(clnt_sock,revbuf,len);
    }
    close(clnt_sock);
    close(serv_sock);

return 0;
}

void error_handling(char *message)
{
fputs(message, stderr);
fputc(\'\\n\', stderr);
exit(1);
}

 

 

在ubuntu系统上执行编译:

gcc client.c -o client

执行客户端程序就是./client

编译服务器程序

gcc server.c -o server

执行服务器程序

./server

 上面的代码只能对应一个客户端连接一个服务器,如果一个服务器要支持多个客户端的请求,请看socke(37)章节的代码

 

我们来看服务器的函数:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h> 

/*
*定义一个宏,输出错误信息并且退出 
*/
#define ERR_EXIT(m) \\
do \\
{\\
perror(m);\\
exit(EXIT_FAILURE);\\
}while(0)

void*thread_exc(void* arg){
    pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程 
   int     clnt_sock = (int)arg;
   char revbuf[1024];
    while(1){
    memset(revbuf,0,sizeof(revbuf));
    int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度 
    if(len == 0){ //说明客户端终止了数据的发送 
        break;
    }
    fputs(revbuf,stdout);
    //读到多少数据就给客户端返回多少字节的数据 
    write(clnt_sock,revbuf,len);
    }
    close(clnt_sock); //记得关闭线程 
}

int main(int argc, char *argv[])
{
    int serv_sock;
    int clnt_sock;
    
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;
    
     
    
    serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
    ERR_EXIT("socket创建失败");
    }
    
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(9999);
    
    if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){
    ERR_EXIT("bind失败");
    }
    //SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接 
    if (listen(serv_sock, SOMAXCONN) == -1){
    ERR_EXIT("listen失败");    
    }
    
    while(1){ //在while循环中一直等待客户端的监听
     
      clnt_addr_size = sizeof(clnt_addr);
       clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
       if (clnt_sock == -1){
          ERR_EXIT("accept失败");    
       }
       //每一个客户端的请求都开启一个线程进行处理
          pthread_t thread_id ;
          int ret;
      //将clnt_sock通过第三个参数传递到线程函数中 
    if((ret = pthread_create(&thread_id,NULL,thread_exc,(void*)clnt_sock)) != 0){
        ERR_EXIT("线程创建失败");
    }
        
    }
    
    
    close(serv_sock);

return 0;
}

void error_handling(char *message)
{
fputs(message, stderr);
fputc(\'\\n\', stderr);
exit(1);
}

 

我们来看客户端的函数:

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

/*
*定义一个宏,输出错误信息并且退出 
*/
#define ERR_EXIT(m) \\
     do \\
     {\\
        perror(m);\\
        exit(EXIT_FAILURE);\\
     }while(0)

int main(int argc, char *argv[])
{
    int serv_sock;
    struct sockaddr_in serv_addr;



    serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
        {
            ERR_EXIT("socket创建失败");
        }


    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(9999);



    if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<0){
        ERR_EXIT("客户端connect失败");
    }
    
       char revbuf[1024];
        char sendbuf[1024];
      memset(revbuf,0,sizeof(revbuf));
      memset(sendbuf,0,sizeof(revbuf));
     while((fgets(sendbuf,sizeof(sendbuf),stdin)!= NULL)){

        write(serv_sock,sendbuf,strlen(sendbuf));
        read(serv_sock,revbuf,sizeof(revbuf));
         fputs(revbuf,stdout);
         //读到多少数据就给客户端返回多少字节的数据 
         memset(sendbuf,0,sizeof(revbuf));
         memset(revbuf,0,sizeof(revbuf));
     }
    close(serv_sock);

    return 0;
}

 

 

对应服务器的函数:有一点很关键在创建线程的的执行函数的入口处调用 pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程 

第二在客户端关闭连接,服务器read字节数为0的时候,记得关闭客户端的连接

close(clnt_sock); //记得关闭线程
}

我们来看程序运行的效果

服务器端收到了客户端1和客户端2的数据

 

 客户端1:

客户端2:

 

这里千万不能将线程的地址传递进行会存在多线程隐患的问题,千万不能写成(void*)&clnt_sock

void*thread_exc(void* arg){
pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程
int clnt_sock = *(*int)arg;

这样会存在多线程隐患的问题,当第一个线程正在执行thread_exc执行int clnt_sock = *(*int)arg或者自己线程的sockid的时候,此时第二个线程创建了成功改变了sockid的值,第一个线程通过*(*int)arg获得的sockid就是刚刚创建的第二个线程的。所以这里存在线程安全问题,所以不能使用指针传递,所以必须使用值传递

但是上面的代码还存在一个小bug

就是将int类型强制转换成了void*类型void*)clnt_sock)存在问题,例如在64位的系统上指针void*是8个字节,int是4个字节,存在转换的问题,可以使用下面的方式进行解决

 

程序的代码修改如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h> 

/*
*定义一个宏,输出错误信息并且退出 
*/
#define ERR_EXIT(m) \\
do \\
{\\
perror(m);\\
exit(EXIT_FAILURE);\\
}while(0)

void*thread_exc(void* arg){
    pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程 
   int     clnt_sock = *((int*)arg);
   //记得关闭指针
   free(arg); 
   char revbuf[1024];
    while(1){
    memset(revbuf,0,sizeof(revbuf));
    int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度 
    if(len == 0){ //说明客户端终止了数据的发送 
        break;
    }
    fputs(revbuf,stdout);
    //读到多少数据就给客户端返回多少字节的数据 
    write(clnt_sock,revbuf,len);
    }
    close(clnt_sock); //记得关闭线程 
}

int main(int argc, char *argv[])
{
    int serv_sock;
    int clnt_sock;
    
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;
    
     
    
    serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
    ERR_EXIT("socket创建失败");
    }
    
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(9999);
    
    if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){
    ERR_EXIT("bind失败");
    }
    //SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接 
    if (listen(serv_sock, SOMAXCONN) == -1){
    ERR_EXIT("listen失败");    
    }
    
    while(1){ //在while循环中一直等待客户端的监听
     
      clnt_addr_size = sizeof(clnt_addr);
       clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
       if (clnt_sock == -1){
          ERR_EXIT("accept失败");    
       }
       //每一个客户端的请求都开启一个线程进行处理
          pthread_t thread_id ;
          int ret;
      //将clnt_sock通过第三个参数传递到线程函数中 
      int * p = (int*)malloc(sizeof(int));
      *p = clnt_sock;
    if((ret = pthread_create(&thread_id,NULL,thread_exc, p ))!= 0){
        ERR_EXIT("线程创建失败");
    }
        
    }
    
    
    close(serv_sock);

return 0;
}

void error_handling(char *message)
{
fputs(message, stderr);
fputc(\'\\n\', stderr);
exit(1);
}

 

 

 

 

以上是关于linux网络编程-socket的主要内容,如果未能解决你的问题,请参考以下文章

Linux----网络编程socket

Linux:TCP Socket编程(代码实战)

Linux:TCP Socket编程(代码实战)

Linux:UDP Socket编程(代码实战)

Linux:UDP Socket编程(代码实战)

Linux 网络编程三(socket代码详解)