Linux 网络编程 套接字

使用UDP协议进行数据传输

UDP服务器端
  • 创建 socket–与网卡驱动建立联系–通过返回的socket描述符来进行操作;接下来的所有操作都是通过这个描述符完成的

参数

domain–指定使用那个版本的协议进行通信-AF_INET(ipv4)

type–套接字类型:流式套接字;数据报套接字

protoc–协议类型:IPPROTO_TCP=6 ; IPPROTO_UDP=17

sockfd = socket(AF_INET,SOCK_DGRAM, IPPROTO_UDP);

  • 绑定过程为套接字绑定地址和端口信息
//struct sockaddr_in  //ipv4
//struct sockaddr_in6  //ipv6

    int sockfd = -1;
    sockfd = socket(AF_INET,SOCK_DGRAM, IPPROTO_UDP);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9000);

//inet_addr---将字符串的IP地址转换为网络主机地址
    addr.sin_addr.s_addr = inet_addr("192.168.122.131");

   if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind");
        return -1;
    }
  • 接受数据–接收客户端发送的消息,并将客户端的地址放到addr中
char buff[1024] = {0};

struct sockaddr_in cli_addr;
socklen_t len = sizeof(struct sockaddr_in);
recvfrom(sockfd, buff, 1023, 0,
            (struct sockaddr*)&cli_addr, &len);
//recvfrom返回实际接收的数据长度
//flag默认给0,阻塞式等待数据
//addr 获取客户端的地址
//&len获取的是实际的 信息长度
  • 发送消息
sendto(sockfd, tmp, strlen(tmp), 0, addr, len)
//返回实际发送的数据长度
//addr 将数据发送给addr这个地址
  • 关闭
close(sockfd);

注:

inet_addr –将字符串的ip地址转换为网络主机地址


UDP客户端

客户端不推荐手动绑定地址:绑定地址有可能会失败导致无法发送

    1. 创建socket
    1. 定义服务器地址
    1. 向服务器地址发送数据
    1. 接收数据
    1. 关闭socket

      • 注:发送数据:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
//1.创建socket
int sockfd = -1;
sockfd = socket(AF_INET,SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < -1)
{
    perror("socket");
    return -1;
}

//2.定义服务器地址
struct sockaddr_in serv_addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9000);
addr.sin_addr.s_addr = inet_addr("192.168.2.2");

socklen_t len = sizeof(sockaddr_in);

//3.向server发送数据
while(1)
{
    char buf[1024]={};
    scanf("%s",&buf);
    sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serv_addr,len);
    //4.接收数据
    char tmp[1024] = {0};
    struct sockaddr_in addr;
    recvfrom(sockfd, tmp, 1023, 0,
            (struct sockaddr*)&addr, &len);
    printf("serv say:%s\n", tmp);
}
//5.关闭socket
close(sockfd);

TCP-服务端

  1. 创建套接字(sip sport),绑定地址接收数据。(原始的socket不接受普通数据,仅仅建立连接)
  2. 为套接字绑定地址
  3. 开始监听套接字

客户端 SYN ->服务端 客户端发起连接请求

客户端 <-ACK SYN 服务端 服务端发起连接并响应

客户端 ACK-> 服务端 客户端响应
4. 接收连接成功后的新socket(sip sport dip dport),对于每一个客户端都有自己的一个socket,每个客户端发送数据过来都有自己的一块新缓冲区用来存储数据;
5. 收发数据
6. 关闭 socket

连接成功队列:队列长度有限,同一时间的最大连接数,队列中取走一个,可以再加一个。
了解:泛洪攻击

header 1
row 1 col 1
row 2 col 1

注意:如何判断TCP连接断开?


  • 对于接收方来说:如果连接断开,recv返回值为0,代表连接断开
  • 对于发送方来说:如果连接断开,每次调用send都会触发连接断开异常,接收到系统发送的SIGPIPE信号,导致进程推出

如果不想让进程退出,那么需要在网络程序运行初始化时对SIGPIPE信号做出自定义/忽略处理。
int main(int argc, char *argv[])
{
    int lst_sockfd = -1;

    if (argc != 3) {
        printf("Usage: ./tcp_server ip port\n");
        return -1;
    }
    //创建一个拉皮条的socket---监听socket
    lst_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (lst_sockfd < 0) {
        perror("socket error");
        return -1;
    }
    //为监听socket绑定一个监听的地址
    struct sockaddr_in lst_addr;
    lst_addr.sin_family = AF_INET;
    lst_addr.sin_port = htons(atoi(argv[2]));
    lst_addr.sin_addr.s_addr = inet_addr(argv[1]);
    //inet_aton(argv[1], &lst_addr.sin_addr);
    //inet_pton(AF_INET, argv[1], &lst_addr.sin_addr);
    socklen_t len = sizeof(struct sockaddr_in);
    int ret = bind(lst_sockfd, (struct sockaddr*)&lst_addr, len);
    if (ret < 0) {
        perror("bind error");
        return -1;
    }
    //开始监听socket
    if (listen(lst_sockfd, 5) < 0) {
        perror("listen error");
        return -1;
    }
    while(1) {
        int new_fd;
        struct sockaddr_in cli_addr;
        //从已成功连接队列取出一个新连接
        //假如这个队列里边没有新连接就会阻塞等待客户端的连接
        new_fd = accept(lst_sockfd, (struct sockaddr*)&cli_addr,
                &len);
        if (new_fd < 0) {
            perror("accept error");
            continue;
        }
        //接收新连接客户端的数据
        //这个新的连接里边已经定义好了数据通信的源地址端口和
        //目的地址端口,因此在接收或发送数据的时候,就不需要
        //重新确定了。
        while(1) {
            char buff[1024] = {0};
            ssize_t r_len = recv(new_fd, buff, 1023, 0);
            if (r_len == 0) {
                printf("peer shutdown!!\n");
                break;
            }
            printf("client:%s:%d say:%s\n", 
                    inet_ntoa(cli_addr.sin_addr), 
                    ntohs(cli_addr.sin_port),
                    buff);
            /*
            //向新连接的客户端回复数据
            printf("server say:");
            fflush(stdout);
            char tmp[1024] = {0};
            scanf("%s", tmp);
            send(new_fd, tmp, strlen(tmp), 0);
            */
        }
        close(new_fd);
    }
    close(lst_sockfd);
    return 0;
}

TCP客户端

1.创建socket

socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

2.向服务器端发起连接
3.收发数据
4.关闭socket

void sigcb(int signo)
{
    printf("recv a signal:SIGPIPE\n");
}
int main(int argc, char *argv[])
{
    int sockfd = -1;
    if (argc != 3) {
        printf("Usage: ./tcp_client ip port\n");
        return -1;
    }
    signal(SIGPIPE, sigcb);
    //1. 创建socket
    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd < 0) {
        perror("socket error");
        return -1;
    }
    //向服务器端发起连接
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[2]));
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t len = sizeof(struct sockaddr_in);
    int ret = connect(sockfd, (struct sockaddr*)&serv_addr, len);
    if (ret < 0) {
        perror("connect error");
        return -1;
    }
    //连接建立成功之后,sockfd将确定下来,操作的数据应该从哪来,
    //到哪去 ,所以收发数据,将不用再确定从哪发,发给谁
    while(1) {
        //向服务器端发送数据
        //printf("client say:");
        //fflush(stdout);
        char buff[1024] = {0};
        //scanf("%s", buff);
        sprintf(buff, "%s", "zheshiyige ceshi shuju!!");
        send(sockfd, buff, strlen(buff), 0);
        sleep(1);
        //接收服务器端的回复
        /*
        char tmp[1024] = {0};
        recv(sockfd, tmp, 1023, 0);
        printf("server say:%s\n", tmp);
        */
    }
    close(sockfd);
    return 0;
}
多进程版的服务端程序
void handle(int sockfd, struct sockaddr_in addr) 
{
    while(1) {
        char buff[1024] = {0};
        int ret = recv(sockfd, buff, 1023, 0);
        if (ret <= 0) {
            if (errno == EINTR || errno == EAGAIN) {
                usleep(1000);
                continue;
            }
            close(sockfd);
            exit(0);
        }
        printf("client:[%s:%d] say:%s\n", 
                inet_ntoa(addr.sin_addr),
                ntohs(addr.sin_port), buff);
        send(sockfd, "i am robot~!!!", 14, 0);
    }
    close(sockfd);
    return ;
}
int create_worker(int sockfd, struct sockaddr_in addr)
{
    int pid = -1;

    pid = fork();
    if (pid < 0) {
        close(sockfd);
        return -1;
    }else if (pid == 0) {
        //启动子进程来处理于客户端的聊天
        //如果子进程一直不退出,则父进程会一直等待他
        if (fork() == 0) {
            //而孙子进程处理完毕聊天后,会退出,资源被init进程回收
            //因为它的父进程已经退出,它变成了孤儿进程
            handle(sockfd, addr);
        }
        //让子进程直接退出,是为了让父进程的等待直接返回
        exit(0);
    }else {
        //父进程记得关闭描述符,否则会占据资源不释放
        //同时也害怕出现跟子进程同时操作的混乱问题
        close(sockfd);
        //为了防止子进程成为僵尸进程,而不释放资源
        wait(NULL);
    }
    return 0;
}
int main(int argc, char *argv[])
{
    int lst_fd = -1;

    if (argc != 3) {
        printf("Usage: ./ptcp_server ip port\n");
        return -1;
    }
    lst_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (lst_fd < 0) {
        perror("socket error");
        return -1;
    }
    struct sockaddr_in lst_addr;
    lst_addr.sin_family = AF_INET;
    lst_addr.sin_port = htons(atoi(argv[2]));
    lst_addr.sin_addr.s_addr = inet_addr(argv[1]);

    socklen_t len = sizeof(struct sockaddr_in);
    int ret = bind(lst_fd, (struct sockaddr*)&lst_addr, len);
    if (ret < 0) {
        perror("bind error");
        return -1;
    }
    if (listen(lst_fd, 5) < 0) {
        perror("listen error");
        return -1;
    }
    while(1) {
        int newfd = -1;
        struct sockaddr_in cli_addr;
        newfd = accept(lst_fd, (struct sockaddr*)&cli_addr, &len);
        if (newfd < 0) {
            perror("accept error");
            continue;
        }
        create_worker(newfd, cli_addr);
    }
    return 0;
}
多线程版的服务端程序
/*
 * 基于TCP的一个多线程简单网络聊天程序
 *  1. 在接收一个客户端新连接后启动一个线程机器人来聊天
 */
void* handle(void *arg)
{
    int sockfd = (int)arg;
    pthread_t tid = pthread_self();

    while(1) {
        char buff[1024] = {0};
        int ret = recv(sockfd, buff, 1023, 0);
        if (ret <= 0) {
            break;
        }
        printf("client:%lu say: %s\n", tid, buff);
        send(sockfd, "i don't konw!!", 14, 0);
    }
    close(sockfd);
    return NULL;
}
int create_robot(int sockfd)
{
    pthread_t tid;
    pthread_create(&tid, NULL, handle, (void*)sockfd);
    pthread_detach(tid);

    return 0;
}
int main(int argc, char *argv[])
{
    int lstfd = -1;

    lstfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (lstfd < 0) {
        perror("socket error");
        return -1;
    }
    struct sockaddr_in lst_addr;
    lst_addr.sin_family = AF_INET;
    lst_addr.sin_port = htons(atoi(argv[2]));
    lst_addr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t len = sizeof(struct sockaddr_in);

    int ret = bind(lstfd, (struct sockaddr*)&lst_addr, len);
    if (ret < 0) {
        perror("bind error");
        return -1;
    }
    if (listen(lstfd, 5) < 0) {
        perror("listen error");
        return -1;
    }
    while(1) {
        int newfd = -1;
        struct sockaddr_in cli_addr;
        newfd = accept(lstfd, (struct sockaddr*)&cli_addr, &len);
        if (newfd < 0) {
            perror("accept error");
            continue;
        }
        create_robot(newfd);
    }
    return 0;
}

TCP三次握手过程

四次挥手过程

time_wait状态;2MSL

netstat -anptu

TCP和UDP的对比:

有连接 无连接

可靠的 不可靠的

字节流(内部数据可以拆分来发) 数据报(整体,不可拆分包)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值