1.前言

        在linux下,系统系统了与socket相关的方法和类,这些函数可以通过socket文件描述符操作socket文件,实现linux下的网络编程。

2.函数和结构体的讲解

Socket结构体

//套接字地址信息
struct sockaddr {
      unsigned short sa_family; // 地址家族, AF_xxx
      char sa_data[14]; //14字节协议地址xxx.xxx.xxx.xxx
};

//Internet网络的结构体 _in标识internet
struct sockaddr_in {
      short int sin_family; // 通信类型,我们通常使用AF_INET.
                              //  AF_INET是IPv4的网络协议套接字类型,.
                              //  具有通用性,windows系统和unix系统都有这个类型
      unsigned short int sin_port; // 端口号。范围是0-65535

      struct in_addr sin_addr; // Internet 地址

      unsigned char sin_zero[8]; //用于保存sockadd数据,需要使用memset置零
};

//Internet地址xxx.xxx.xxx.xxx转换而来的数据
struct in_addr {
      unsigned long s_addr; //通过unsigned long inet_addr("xxx.xxx.xxx.xxx")转换而来
};

地址转换函数

//地址转换函数

//功能:
//将ip地址按照想要的格式进行转换,用于填写地址

//函数和头文件
#include <arpa/inet.h>

//地址转换成unsigned long,addr是ip地址"xxx.xxx.xxx.xxx"
unsigned long inet_addr(const char* addr);

//struct inddr 转换成char*,in是internet地址sin_addr
char*inet_ntoa(struct in_addr in); 

//示例:
inet_addr("127.0.0.1"); //16777343;
inet_ntoa(addr.sin_addr); //127.0.0.1;

创建socket函数

//创建sokect函数socket()

//功能:
//创建一个socket,返回socket文件的文件描述符

//函数和头文件
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

/*
参数,返回值:
domain:协议族,通常写AF_INET
type:传输方式,字节流填SOCK_STREAM 数据报填SOCK_DGRAM
protocol:使用的协议,通常有IPPROTO_TCP, IPPROTO_UDP,  IPPROTO_IP
返回值:返回一个文件描述符,如果失败,则返回-1
*/

//示例:
//创建一个TCP的socket
int fds = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fds == -1) {
    return -1;
}

绑定函数

 //绑定函数bind()

 //功能
 //将地址绑定到socket文件上,
 //传入文件描述符,地址和地址长度,
 //成功返回0,失败返回-1

//函数和头文件
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
/*
//参数,返回值
sockfd:通过socket()得到的文件描述符
my_add: 绑定地址.struct sockaddr指针
addrlen:地址长度,通常写sizeof(struct sockaddr)
返回值:成功返回0 失败返回-1
*/

//示例:
bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr));

连接服务器

//链接程序connect()

/*
//功能
客户端向申请服务器建立连接,
填入服务器的地址和地址长度,
建立连接失败返回-1,成功返回0
*/

//函数和头文件

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *servAddr, int addrlen);

/*
//参数,返回值
sockfd:通过socket()得到的文件描述符
my_add: 绑定目标地址.struct sockaddr指针
addrlen:地址长度,通常写sizeof(struct sockaddr)
返回值:成功返回0,失败返回-1
*/

//示例:
connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr));

监听

//监听函数listen()

/*
//功能
设置监听的端口和最大连接数目
*/

//函数和头文件
#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);

/*
//参数,返回值
sockfd:通过socket()得到的文件描述符
backlog:允许进入队列的最大连接数目,最大值为128
返回值:成功返回0,失败返回-1
*/

//示例:
listen(socketFds, 2);

等待操作

//等待操作accept()

/*
//功能                                                                                                                                                                                     
在等待接收的时候会阻塞在这个函数上,直到有客户端连接成功或者传输数据,
返回客户端的socket文件描述符,并保存地址信息。
*/

//函数和头文件
#include <sys/socket.h>

int accept(int sockfd, void *addr, int *addrlen);

/*
//参数,返回值
sockfd:通过sokect()函数
addr:接收到的客户端的sockaddr_in的指针
addrlen:接收到的客户端的sockeaddr_in的长度的指针
*/

//示例
int clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);

数据的发送

//数据的发送send(), write()

/*
//功能
将数据发送给指定的客户端,传入客户都拿socket文件描述符,数据,数据大小,
*/

//函数和头文件
#include <sys/socket.h>

int send(int sockfd, const void *msg, int len, int flags);

int write(int sockfd, const void *msg, int len);

/*
//参数和返回值
sockfd:接收端socket文件描述符
*msg:发送的数据缓冲区指针
len:数据长度

返回值:发送成功返回0,失败返回-1
*/

//示例
char buf[] = "啦啦啦";
if (-1 == send(fd, buf, sizeof(buf), 0)) {
    return -1;
}

if (-1 == write(fd, buf, sizeof(buf))) {
    return -1;
}

数据的接收

//数据的接收 read(), recv()

/*
//功能
客户端通过读文件socket文件描述符指定的文件,
将数据放到缓存中,并保存缓存的指针和长度,
在等待接收的时候会阻塞在这个函数中,
函数每次将长度为len的数据推到缓冲区buf中。
*/

//函数和头文件
#include <sys/socket.h>

int read(int sockfd, const void *msg, int len, int flags);

int recv(int sockfd, const void *msg, int len);

/*
//参数和返回值
sockfd:客户端童工socket()函数得到的文件描述符
msg:buf缓存的头指针,函数会将长度为len数据存到这个缓存中
len:一次性读取的buf的长度

返回值:成功返回0,失败返回-1
*/

UDP的发送和接收

//UDP的发送和接收sendto(), recvfrom()

/*
//功能
发送数据
接收收据
成功返回0,失败返回-1
*/

//函数和头文件
#include <sys/socket.h>

int sendto(int sockfd, const void *msg, \
            int len, unsigned int flags, \
            const struct sockaddr *to, int tolen \
            );

int recvfrom(int sockfd, void *buf, \
            int len, unsigned int flags, \
            struct sockaddr *from, int *fromlen \
            );

/*
//参数和返回值
sockfd:文件描述符
msg:缓冲区的头指针
len:缓冲区大小
flags:通常写0就行
*to:目标地址的指针
tolen:目标地址结构体的大小
*from:源地址的指针
*fromlen:源地址结构的大小
*/

关闭套接字

//关闭套接字close()和shutdown()

/*
//功能
关闭套接字
*/

//函数和头文件
#include <sys/socket.h>

int close(int sockfd);

int shutdown(int sockfd, int how);

/*
//参数和返回值
sockfd:文件描述符
how:关闭的方式,0:不允许接收 1:不允许发送 2:都不允许
*/

非阻塞

//非阻塞fcntl()

/*
//功能
将socket阻塞状态设置成非阻塞状态
在上面的函数中,accept()和read(),recv()都是阻塞的,但是这种阻塞状态并不适合一个高并发
服务器,所以我们要将他设置成非阻塞状态。
*/

//函数和头文件
#include <unistd.h>

#include <fontl.h>

sockfd = socket(AF_INET, SOCK_STREAM, 0);

fcntl(sockfd, F_SETFL, O_NONBLOCK);

/*
非阻塞状态的时候,就不知道是否当前有数据了,所以,这里就引入了IO多路服用的相关知识。
后面会单独讲一下select(),poll(),和epoll().
*/

三.写一个例子

   服务器

/**********************************************************                                                                                                                                
 * Author        : 谢名聪
 * Email         : 869408604@qq.com
 * Last modified : 2022-04-27 14:16
 * Filename      : server.cpp
 * Description   : 写一个简单的例子描述一下
 * sokect的主要结构体,函数,参数
 * *******************************************************/

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

int main(){
    //创建套接字,返回套接字的文件操作符
    int servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    //将套接字和IP、端口绑定
    struct sockaddr_in servAddr;
    memset(&servAddr, 0, sizeof(servAddr));  //每个字节都用0填充
    servAddr.sin_family = AF_INET;  //使用IPv4地址
    servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    servAddr.sin_port = htons(1234);  //端口
    //绑定端口
    if (-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr))) {
        //绑定端口失败
        std::cout << "bind error" << std::endl;
        return -1;
    }

    //监听端口,限制最大队列为20个
    if (-1 == listen(servSock, 20)) {
        //监听失败
        std::cout << "listen error" << std::endl;
    }

    std::cout << "服务器启动...." << std::endl;
    std::cout << "ip:" << inet_ntoa(servAddr.sin_addr) << std::endl;
    std::cout << "port:" << 1234 << std::endl;

    std::cout << "开始监听>>>>" << std::endl;

    //接收客户端请求
    struct sockaddr_in clientAddr; //客户端的地址
    socklen_t clientAddrSize = sizeof(clientAddr);

    std::cout << "阻塞在accept(),等待....." << std::endl;

    int clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
    //向客户端发送数据
    char str[] = "你好客户端,我是服务器";
    //write(clientSock, str, sizeof(str));
    send(clientSock, str, sizeof(str), 0);

    //关闭套接字
    close(clientSock);
    close(servSock);
    std::cout << "关闭套接字" << std::endl;
    return 0;
} 

客户端

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

int main(){
    //创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    //向服务器(特定的IP和端口)发起请求
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    if ( -1 == connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))) {
        std::cout << "建立连接失败" << std::endl;
        return -1;
    }

    std::cout << "阻塞再read(),等待服务器回消息" << std::endl;
    //读取服务器传回的数据
    char buf[40];
    for (int i = 0; i < 20; i++) {
        char buffer[2];
        //read(sock, buffer, sizeof(buffer)-1);
        recv(sock, buffer, sizeof(buffer), 0);
        buf[2 * i] = buffer[0];
        buf[2 * i + 1] = buffer[1];
    }
    std::cout << "客户端接收到消息:" << buf << std::endl;
    //关闭套接字
    close(sock);
    return 0;
}

看看效果吧

先启动服务器

g++ -std=c++11 server.cpp -o server

./server

再启动客户端

g++ -std=c++11 client.cpp =o client

./client

 

 四.小结

        现在我们已经可以实现两个机器的连接和数据传输了,后面我们就要处理多个连接和数据传输,我们已经在今天的非阻塞函数fcntl()中引出了,I/O多路复用的必要性,所以,只要我们把I/O多路复用的问题解决了,我们就可以把网络的基本框架解决了。等我再把I/O多路复用的知识点整理完,我们就正式开始写网络的东西吧。

Logo

更多推荐