Linux网络编程

本文详细介绍了TCP与UDP两种传输协议的主要区别,包括它们的数据可靠性、连接方式和速度等方面。同时,深入讲解了Socket编程的基本步骤,如创建套接字、绑定地址、监听连接、接受连接以及数据的读写操作。通过示例代码展示了服务器如何使用fork进程或线程处理客户端连接,并提供了客户端的连接与通信实现。

目录

TCP与UDP的区别

字节序

socket服务器

创建套接字int socket(int domain, int type, int protocol);

参数:

返回值:

为套接字添加信息(协议族、IP地址、端口号)int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数:

返回值:

监听客户端的连接int listen(int sockfd, int backlog);

参数:

返回值:

等待客户端的连接int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:

返回值:

向客户端发数据write()

从客户端读数据read()

实现

client客户端

创建套接字int socket(int domain, int type, int protocol);

参数:

返回值:

连接服务器int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数:

返回值:

向客户端发数据write()

从客户端读数据read()

实现

 

TCP与UDP的区别

ae76a5ebb7094bc2aaa150f09a5c151d.jpeg

字节序

  • 大端字节序(BE):将高序字节存储在起始位置
  • 小端字节序(LE):将低序字节存储在起始位置
  • 大端字节序与小端字节序的区别:
    19fdd23598094db59d6d72591151cb29.jpeg
  •  TCP协议使用大端字节序,需要将大端字节序转换为网络字节序
    94dee31be1214e639e23f113a41dcf24.jpeg

socket服务器

创建套接字int socket(int domain, int type, int protocol);

参数:

  • int domain:选择协议族(一般使用AF_INET,互联网协议族)
    85f98f8862294a12ba69b2e23d0aa7ee.jpeg
  • int type:选择协议
    edc29404b0c24dd29ab9e5d2a0a73f94.jpeg
  • int protocol:(通常为0, 表示type参数对应的默认协议)
    046917738266400c888f34f9714e0433.jpeg

返回值:

调用成功返回套接字描述符,调用失败返回-1

为套接字添加信息(协议族、IP地址、端口号)int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数:

  • int sockfd:套接字
  • const struct sockaddr *addr:sockaddr类型结构体地址,用来存储互联网协议族、IP地址和端口等信息

    在IPv4因特网域中,存储套接字信息用sockaddr_in类型结构体,因此在传入bind()参数时要对该结构体进行强制转换,将sockaddr_in类型结构体强制转换成sockaddr类型结构体
    997ce9276a8d4c9da514797220a9674a.png
    b44c23d4fb2d4365a5bca82e9fadb8ff.jpeg

    将端口号转化成网络字节序:
    e823b466a0bf4726a14c22d274cfa5c8.png

    将IP地址转化成网络字节序:
    de77abe1d418431f9329bfeb6db95b1d.png
  • socklen_t addrlen:参数二的内存空间大小

返回值:

调用成功返回0,调用失败返回-1

监听客户端的连接int listen(int sockfd, int backlog);

参数:

  • int sockfd:套接字
  • int backlog:指定在请求队列中允许的最大连接数

返回值:

调用成功返回0,调用失败返回-1

等待客户端的连接int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

当没有客户端连接时,accept()会阻塞等待

参数:

  • int sockfd:套接字
  • const struct sockaddr *addr:sockaddr类型结构体地址
  • socklen_t *addrlen:参数二内存空间大小的值的地址
    a99d229497644c83a36fe6f61f57b203.jpeg

返回值:

调用成功返回套接字描述符,返回的套接字描述符用于传输数据,调用失败返回-1

向客户端发数据write()

跟文件编程中的write()没什么区别,使用accept()返回的套接字描述符

从客户端读数据read()

跟文件编程中的read()没什么区别,使用accept()返回的套接字描述符

实现

  • 实现1:
           主进程等待客户端的连接,当客户端连接后,fork()一个子进程不断读取客户端的数据,然后在该子进程里fork()一个子进程向客户端发数据
     
  • 实现2:
           主线程等待客户端的连接,当客户端连接后pthread_create()两个线程来处理,线程A用来读取客户端的数据,线程B用来给客户端发送数据

服务器(fork进程)实现:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <linux/in.h>

int main(int argc,char *argv[])
{
    char writeBuf[128];
    if(argc < 3){
        printf("missing parameter ->IP or ->port number\n");
        exit(-1);
    }

    int netID = socket(AF_INET,SOCK_STREAM,0);

    if(netID < 0){
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in my_net;
    memset(&my_net,0,sizeof(struct sockaddr_in));
    my_net.sin_family = AF_INET;
    my_net.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1],&my_net.sin_addr);

    if(bind(netID,(struct sockaddr *)&my_net,sizeof(struct sockaddr_in)) < 0){
        perror("bind");
        exit(-1);
    }

    if(listen(netID,10) < 0){
        perror("listen");
        exit(-1);
    }

    int msgID;
    struct sockaddr_in msg_addr;
    int tmp = 0;
    int size = sizeof(struct sockaddr_in);

    while(1){
        memset(&msg_addr,0,sizeof(struct sockaddr_in));
        msgID = accept(netID,(struct sockaddr *)&msg_addr,&size);
        if(msgID < 0){
            perror("accept");
        }
        tmp++;
        printf("get connect%d to ID:%s\n",tmp,inet_ntoa(msg_addr.sin_addr));
        if(fork() == 0){//创造子进程进行读取客户端的数据
            if(fork() == 0){//创建子进程向客户端发送数据
                while(1){
                    memset(writeBuf,'\0',sizeof(writeBuf));
                    fgets(writeBuf,128,stdin);
                    write(msgID,writeBuf,strlen(writeBuf));
                }
                exit(0);
            }
            int readSize = 0;
            char readBuf[128];
            while(1){

                memset(readBuf,'\0',sizeof(readBuf));
                readSize = read(msgID,readBuf,128);

                if(readSize < 0){
                    perror("read");
                }else{
                    printf("msg from is %s\n",readBuf);
                }
            }
            exit(0);
        }

    }
    return 0;
}

client客户端

创建套接字int socket(int domain, int type, int protocol);

参数:

  • int domain:选择协议族(一般使用AF_INET,互联网协议族)
    85f98f8862294a12ba69b2e23d0aa7ee.jpeg
  • int type:选择协议
    edc29404b0c24dd29ab9e5d2a0a73f94.jpeg
  • int protocol:(通常为0, 表示type参数对应的默认协议)
    046917738266400c888f34f9714e0433.jpeg

返回值:

调用成功返回套接字描述符,调用失败返回-1

连接服务器int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数:

  • int sockfd:套接字
  • const struct sockaddr *addr:sockaddr类型结构体地址,用来存储互联网协议族、IP地址和端口等信息

    在IPv4因特网域中,存储套接字信息用sockaddr_in类型结构体,因此在传入bind()参数时要对该结构体进行强制转换,将sockaddr_in类型结构体强制转换成sockaddr类型结构体
    997ce9276a8d4c9da514797220a9674a.png
    b44c23d4fb2d4365a5bca82e9fadb8ff.jpeg

    将端口号转化成网络字节序:
    e823b466a0bf4726a14c22d274cfa5c8.png

    将IP地址转化成网络字节序:
    de77abe1d418431f9329bfeb6db95b1d.png
  • socklen_t addrlen:参数二的内存空间大小

返回值:

调用成功返回0,调用失败返回-1

向客户端发数据write()

跟文件编程中的write()没什么区别,使用accept()返回的套接字描述符

从客户端读数据read()

跟文件编程中的read()没什么区别,使用accept()返回的套接字描述符

实现

连接服务器成功后,主进程不断读取服务器数据,fork()一个子进程往客户端发送数据 

客户端实现

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <linux/in.h>

int main(int argc,char *argv[])
{
    if(argc < 3){
        printf("missing parameter ->IP or ->port number\n");
        exit(-1);
    }

    int netID = socket(AF_INET,SOCK_STREAM,0);

    if(netID < 0){
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in my_net;
    memset(&my_net,0,sizeof(struct sockaddr_in));
    my_net.sin_family = AF_INET;
    my_net.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1],&my_net.sin_addr);

    if(connect(netID,(struct sockaddr *)&my_net,sizeof(struct sockaddr_in)) < 0){
        perror("connect");
        exit(-1);
    }
    printf("connect socket is ok\n");
    char writeBuf[128];

    while(1){
        if(fork() == 0){
            while(1){
                memset(writeBuf,'\0',sizeof(writeBuf));
                fgets(writeBuf,128,stdin);
                write(netID,writeBuf,strlen(writeBuf));
            }
            exit(0);
        }
        int readSize = 0;
        char readBuf[128];
        while(1){

            memset(readBuf,'\0',sizeof(readBuf));
            readSize = read(netID,readBuf,128);

            if(readSize < 0){
                perror("read");
            }else{
                printf("msg from is socket:%s\n",readBuf);
            }
        }
        exit(0);
    }


    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值