0


【Linux】Linux下使用套接字进行网络编程

🔥博客主页: 我要成为C++领域大神
🎥****系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

用于网络应用开发,使用系统提供的一套API函数接口,称为套接字函数

所有的I/O设备都被抽象为文件,一切皆文件,Everything is a File,磁盘、网络数据、终端,甚至进程间通信工具管道pipe等都被当做文件对待。

套接字文件描述符包含有IP地址和PORT端口号。

socket创建套接字

函数原型:

int sockfd=socket(AF_INET,SOCKET_STREAM(流式or报式),int protocol(默认流式协议TCP))

成功返回sock_fd,失败返回-1,并设置errno

bind绑定IP和端口

函数原型:

bind(sock_fd,struct sockaddr *addr)
struct sockaddr *addr

是早期的网络通信结构体,为了向前兼容,仍然保留。

struct sockaddr_in addr

网络信息结构体

addr.sin_family=AF_INET
addr.sin_port=大端(8080)
addr.sin_addr.s_addr=大端(ip)

大小端转换常用函数

htons()

小端转大端端口

htonl()

小端转大端IP

ntohs()

大端转小端端口

ntohl()

大端转小端IP

inet_pton(AF_INET,char *ip,void *addr)
inet_ntop(AF_INET,void *addr,char *ip,16)

绑定可以对socket设置自定义的IP和端口号,其次当绑定某端口的进程退出,可以临时禁用端口号,禁用时长为2MSL。

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

listen网络事件监听(TCP)

函数原型:

listen(int sockfd,int backlog)

backlog:监听序列数

成功返回0,错误返回-1,并设置errno,用于错误处理

connet请求TCP连接函数(主动端)

函数原型:

connect(int mysockfd,struct sockaddr * dest,socklen_t addrlen)

客户端调用该函数向服务器发起TCP连接请求

成功返回0,错误返回-1,并设置errno,用于错误处理

Accept等待TCP连接请求,完成连接(被动端)

Accept为阻塞等待连接,每次只能连接一个客户端,如果要多次连接,要执行多次该函数

函数原型:

int client_fd=accpet(int server_fd,sockaddr * clientAddr(output),socklen_t *Addrlen(intput|output))

arg[1] 包含自身网络信息的socket

arg[2] 连接成功,传出保存客户端的网络信息(IP,port)

arg[3] 传入可以接收的网络信息大小,传出实际大小

成功返回请求端的socketfd,错误返回-1,并设置errno,用于错误处理

CS架构中,服务器socket只有一个,客户端socket有多个(server_fd用于连接,client_fd用于与客户端交互数据)

recv读取数据

在Linux下,read也可以读取socket数据(TCP),因为在Linux下,套接字以文件描述符的形式记录。

函数原型:

ssize_t recv(int sockfd,void *buf,size_t size,int flag)

成功返回读取的数据量,失败返回-1

可以通过将flag设置为

MSG_DONTWAIT

实现非阻塞读取数据

send发送数据

在Linux下,write也可以发送socket数据(TCP),因为在Linux下,套接字以文件描述符的形式记录。

函数原型:

ssize_t send(int sockfd,void *buf,size_t len,int flag)
面试题:

在编写一个服务端-客户端模型时,客户端异常退出,服务端也异常退出。

原因

当客户端异常退出时,客户端关闭了它的读端文件描述符。此时,如果服务端尝试向管道或套接字写数据,会触发

SIGPIPE

信号,默认行为是终止进程,因此服务端也会异常退出。

解决方案

可以在

send()

函数中使用

MSG_NOSIGNAL

标志位,来忽略

SIGPIPE

信号,从而避免服务端异常退出

send(int sockfd, buffer, len, MSG_NOSIGNAL);

使用

MSG_NOSIGNAL

标志位,写操作在对端关闭连接的情况下不会触发

SIGPIPE

信号,而是会返回

EPIPE

错误,程序可以根据这个错误码进行处理而不是直接终止。

在公网上利用TCP协议实现网络数据传输

由于服务端和客户端不在同一个局域网下,所以需要通过互联网进行通信,虚拟机要配置网络连接。

服务端:

服务端是运行在公网上的阿里云服务器。

#include<stdio.h>#include<stdlib.h>#include<arpa/inet.h>#include<sys/socket.h>#include<string.h>#define _SERVER_IP "xxx.xxx.xxx.xxx"#define _PORT 8080#define _BACKLOG 128#define _SHUTDOWN 1#define _TRUE 1#define _FALSE 0#define _IPSIZE 16#define _RECVLEN 1500intmain(){
        structsockaddr_in serverAddr,clientAddr;
        int server_fd;
        int client_fd;
        char Result[_RECVLEN];
        char client_ip[_IPSIZE];
        socklen_t Addrlen;
        serverAddr.sin_family=AF_INET;
        serverAddr.sin_port=htons(_PORT);
        serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
        server_fd=socket(AF_INET,SOCK_STREAM,0);
        bind(server_fd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
        listen(server_fd,_BACKLOG);
        printf("Test TCP Server Version 1.1.0 is Running...\n");
        while(_SHUTDOWN)
        {
        Addrlen=sizeof(clientAddr);
        if((client_fd=accept(server_fd,(struct sockaddr*)&clientAddr,&Addrlen))>0)
        {
           bzero(Result,sizeof(Result));
           bzero(client_ip,sizeof(client_ip));
           inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,client_ip,_IPSIZE);
           printf("Connection From :IP[%s],PORT[%d]\n",client_ip,ntohs(clientAddr.sin_port));
           sprintf(Result,"Hi [%s] Welcome to my TCP test server!service version 1.1.0...",client_ip);
           send(client_fd,Result,strlen(Result),0);
           close(client_fd);
        }
        else
        {
        perror("accpet failed");
        close(server_fd);
        exit(0);
        }
        }
        close(server_fd);
        return0;
}

客户端:

客户端是运行在本地上的虚拟机镜像

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<sys/socket.h>#include<arpa/inet.h>//客户端源码编写,连接服务器成功,服务器反馈信息#define _IP "xxx.xxx.xxx.xxx"#define _PORT 8080intmain(){
    structsockaddr_in ServerAddr;
    bzero(&ServerAddr,sizeof(ServerAddr));
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_port=htons(_PORT);
    inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);
    
    int Myfd=socket(AF_INET,SOCK_STREAM,0);
    //看需求决定是否要绑定char Response[1024];//存放服务端反馈信息ssize_t recvlen;
    bzero(Response,sizeof(Response));

    if((connect(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)
    {
        if((recvlen=recv(Myfd,Response,sizeof(Response),0))>0)
        {
            printf("%s\n",Response);
        }
    }
    else
    {
        printf("Connect failed\n");
        close(Myfd);
        exit(0);
    }
    close(Myfd);
    printf("Client is Over\n");
    return0;
}

运行结果:

函数二次包裹

在我们编写代码时,通常需要根据函数的返回值来判断函数调用情况,这个过程需要大量的if else语句,减少了代码阅读的简洁性。所以为了方便我们阅读,需要对函数进行二次包裹。

什么是二次包裹?

在自定义的头文件中重新声明一个函数,函数名与接口函数不同,返回值、参数均与接口函数相同。在相应的源文件中定义我们的函数,将逻辑判断的过程放在函数中。这样在调用函数时就省去大量的if else判断

#ifndef __MySock_H__#define __MySock_H__#include<sys/socket.h>#include<arpa/inet.h>#include<unistd.h>#include<stdio.h>#include<string.h>#include<stdlib.h>intSOCKET(int domain,int type,int protocol);
intBIND(int sockfd,struct sockaddr* addr,socklen_t addrlen);
ssize_tRECV(int sockfd,void *buf,size_t len,int flags);
ssize_tSEND(int sockfd,void *buf,size_t len,int flags);
intCONNECT(int sockfd,struct sockaddr *addr,socklen_t addrlen);
intACCEPT(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
intLISTEN(int sockfd,int backlog);

#endif
#include"MySock.h"intSOCKET(int domain,int type,int protocol){
    int reval=socket(domain,type,protocol);    
    if(reval==-1)
    {
     perror("socket call failed");   
     exit(0);
    }
     return reval;
}
intBIND(int sockfd,struct sockaddr* addr,socklen_t addrlen){
    int reval=bind(sockfd,addr,addrlen);
    if(reval==-1)
    {
        perror("bind call failed");
        exit(0);
    }
    return reval;
}
ssize_tRECV(int sockfd,void *buf,size_t len,int flags){
    ssize_t reval;
    reval=recv(sockfd,buf,len,flags);
    if(reval==0)
        perror("recv call failed");
    return reval;
}
ssize_tSEND(int sockfd,void *buf,size_t len,int flags){
    ssize_t reval;
    reval=send(sockfd,buf,len,flags);
    if(reval==-1)
        perror("send call failed");
    return reval;
}
intCONNECT(int sockfd,struct sockaddr *addr,socklen_t addrlen){
    int reval=connect(sockfd,addr,addrlen);
    if(reval==-1)
    {
        perror("connect call failed");
        exit(0);
    }
    return reval;
}

intACCEPT(int sockfd,struct sockaddr* addr,socklen_t *addrlen){
    int reval=accept(sockfd,addr,addrlen);
    if(reval==-1)
    {
        perror("accept call failed");
        exit(0);
    }
    return reval;
}
intLISTEN(int sockfd,int backlog){
    int reval=listen(sockfd,backlog);
    if(reval==-1)
    {
        perror("listen call failed");
        exit(0);
    }
    return reval;
}

使用包裹后的函数对我们TCP通信的客户端进行改写:

代码看起来简洁多了

#include"MySock.h"//客户端源码编写,连接服务器成功,服务器反馈信息#define _IP "xxx.xxx.xxx.xxx"#define _PORT 8080intmain(){
    structsockaddr_in ServerAddr;
    bzero(&ServerAddr,sizeof(ServerAddr));
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_port=htons(_PORT);
    inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);
    
    int Myfd=SOCKET(AF_INET,SOCK_STREAM,0);
    //看需求决定是否要绑定char Response[1024];//存放服务端反馈信息ssize_t recvlen;
    bzero(Response,sizeof(Response));

    if((CONNECT(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)
    {
        if((recvlen=recv(Myfd,Response,sizeof(Response),0))>0)
        {
            printf("%s\n",Response);
        }
    }
    close(Myfd);
    printf("Client is Over\n");
    return0;
}
标签: linux 网络 服务器

本文转载自: https://blog.csdn.net/Coldreams/article/details/140024879
版权归原作者 我要成为C++领域大神 所有, 如有侵权,请联系我们删除。

“【Linux】Linux下使用套接字进行网络编程”的评论:

还没有评论