目录

 

服务器端

第一步:创建用于监听的套接字

第二步:将套接字与本机IP地址和端口号绑定

第三步:设置监听

第四步:等待并接收连接请求

第五步:C/S网络通信

第六步:关闭文件描述符

客户端

第一步:创建套接字

第二步:连接服务器

第三步:通信


服务器端

第一步:创建用于监听的套接字

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

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

      domain:地址族,常用有AF_INET和AF_INET6,分别代表IPv4和IPv6

      type:数据传输方式/套接字类型,常用SOCK_STREAM(流格式套接字/面向连接的套接字),SOCK_DGRAM(数据报套接字/无连接的套接字)

      protocol:传输协议,常用IPPROTO_TCP,IPPROTO_UDP,分贝代表TCP和UDP协议

type与protocol不能冲突,即如果使用面向连接的套接字,那么protocol就得是TCP协议。通常protocol参数可以直接填0,这样系统会自动推导出要使用得协议。

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字

      返回值:成功返回文件描述符(当前进程文件描述符表中最小可用的文件描述符),失败返回-1并设置errno。

第二步:将套接字与本机IP地址和端口号绑定

因为套接字相当于是一个在内核中打开的文件,也就是内存中的一篇缓冲区,现在我们要将该缓冲区与本地IP地址和端口号绑定。

首先将IP地址和端口号放入sockaddr_in结构体变量之中

struct sockaddr_in {
    short int sin_family;                      /* Address family */
    unsigned short int sin_port;       /* Port number */
    struct in_addr sin_addr;              /* Internet address */
    unsigned char sin_zero[8];         /* Same size as struct sockaddr */
};

首先定义结构体变量:

struct sockaddr_in serv_addr;

sin_family是使用的地址协议族,一般是AF_INET,即IPv4协议

serv_addr.sin_family = AF_INET;

sin_port是端口号,htons()函数表示将16位的短整数从主机字节序变成网络字节序(即大端模式)。

serv_addr.sin_port = htons(9999); // 本地端口,25536以下

sin_addr存储IP地址,但是不是直接存储成整型数之类的,而是存储到in_addr结构体中。

in_addr结构体如下,可以保存一个32位的IP地址,明明可以直接用,还非要放到一个结构体中,据说是上古时期实现这个东西的程序员想多了。

struct in_addr {
    unsigned long s_addr;
};

这里INADDR_ANY即0.0.0.0,即任意地址,因为有些主机有多块网卡可以连到多个网络有不同ip,这样设置可以让服务器程序监听多个网卡ip地址的端口,也可以理解为用多个ip地址监听同一个端口。

serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

sin_zero是为了让sockaddr结构体与sockaddr_in结构体大小相同,而特意保留的空字节。

将IP地址和端口号放入sockaddr_in结构体变量之后就可以用bind函数将套接字变量和ip,端口号绑定。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,
           socklen_t addrlen);

sockfd:用于监听的套接字文件描述符

addr:是sockaddr结构体类型的变量地址,前边我们是用sockaddr_in结构体来设置的IP地址和端口号,需要强制类型转换成为sockaddr类型的地址。之所以不直接用sockaddr来设置IP地址和端口号是有原因的,struct sockaddr是通用的套接字地址形式,而不同的网络体系表示地址的方法是不同的,struct sockaddr_in是在通用的套接字地址形式的基础上,针对TCP/IP设计的套接字的地址形式,里面多了两个成员,也就是IP号和端口号,这样方便我们填充。

addrlen:sockaddr_in结构体变量的大小。

int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

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

第三步:设置监听

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

sockfd:文件描述符

backlog:未完成连接队列和已完成连接队列的上限,暂时理解为对客户端连接数量的限制。

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

ret = listen(lfd, 64);

     

第四步:等待并接收连接请求

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:用于监听的套接字文件描述符

addr:sockaddr类型的传出参数,存放客户端的套接字变量信息

addrlen:addr的大小

addr与addrlen都是可选参数

返回值:

       如果监听的队列中无等待连接,且套接字为阻塞,则等待连接到来

       如果监听的队列中无等待连接,且套接字为非阻塞,则返回-1并设置errno

       如果监听的队列中有等待连接,则取出第一个连接,返回一个与用于监听的套接字同等类型的套接字(本质是一个文件描述符,用于通信)。

// 等待并接收请求连接请求
    struct sockaddr_in cline_addr;
    socklen_t clien_len = sizeof(cline_addr);
    int cfd = accept(lfd, (struct sockaddr*)&cline_addr, &clien_len);
    if(cfd == -1){
        perror("accept error");
        exit(1);
    }

接收到连接请求之后可以输出展示一下,注意要把ip地址的格式转化成字符串输出

#include <arpe/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);     //将点分十进制的ip地址转化为用于网络传输的数值格式
        返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
 
const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);     //将数值格式转化为点分十进制的ip地址格式
        返回值:若成功则为指向结构的指针,若出错则为NULL
char ipbuf[64];
    printf("client ip: %s, port: %d\n", inet_ntop(AF_INET, &cline_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)), ntohs(cline_addr.sin_port));

第五步:C/S网络通信

上边的过程只是通过套接字将服务器端和客户端连接了起来,具体的通信就是服务器端进程和客户端进程之间的通信,与普通的unix IO并没有区别,也是通过文件描述符与相关的系统调用进行读写。即像操作普通文件一样用read来读取客户端发送的内容,用write来向客户端写内容。

第六步:关闭文件描述符

关闭用于监听和用于通信的文件描述符。

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

int main(int argc, const char* argv[]){
    // 创建用于监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socker error");
        exit(1);
    }

    // 套接字与IP和端口绑定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(9999); // 本地端口,25536以下
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定IP?

    int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1){
        perror("bind error");
        exit(1);
    }

    // 设置监听
    ret = listen(lfd, 64);
    if(ret == -1){
        perror("listen error");
        exit(1);
    }
    
    // 等待并接收请求连接请求
    struct sockaddr_in cline_addr;
    socklen_t clien_len = sizeof(cline_addr);
    int cfd = accept(lfd, (struct sockaddr*)&cline_addr, &clien_len);
    if(cfd == -1){
        perror("accept error");
        exit(1);
    }

    char ipbuf[64];
    printf("client ip: %s, port: %d\n", inet_ntop(AF_INET, &cline_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)), ntohs(cline_addr.sin_port));
    

    // 通信
    while(1){
        // 先接收数据
        char buf[1024] = {0};
        int len = read(cfd, buf, sizeof(buf));
        if(len == -1){
            perror("read error");
            break;
        }else if(len > 0){
            // 顺利读出数据
            printf("read buf = %s\n", buf);
            // 数据小写变大写
            for(int i = 0; i < len; ++i){
                buf[i] = toupper(buf[i]);
            }
            printf("大写转换结果:%s\n", buf);
            // 大写数据发回给客户端
            write(cfd, buf, strlen(buf) + 1);
        }else if(len == 0){
            printf("client disconnect \n");
            break;
        }
    }

    close(lfd);
    close(cfd);


}

 

 

客户端

第一步:创建套接字

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

第二步:连接服务器

先定义sockaddr_in结构体变量来设置ip和端口号

注意这里的ip是服务器端的ip,端口号也是服务器进程对应的端口号

inet_pton用来设置ip地址,可以把字符串变成二进制网络字节序,这里的“127.0.0.1”是本地环回地址,因为我们的服务器进程也在本机上,因此要使用本地环回。

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9999); // 在服务器进程中设置的端口?
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);

接下来是使用connect函数连接服务器,当使用tcp协议的时候,connect会完成三次握手的过程

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

sockfd:客户端进程套接字的文件描述符

addr:服务端的ip和端口号结构变量地址,但是需要注意强转,因为我们定义的都是sockaddr_in变量,而使用的参数类型是sockaddr类型的。

addrlen:结构变量的大小

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

int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1){
        perror("connect error");
        exit(1);
    }

第三步:通信

在服务端进程中,监听和通信用的是两个不同的套接字,而在客户端只需要一个通信套接字就可以,不需要监听的。

注意strlen()函数计算字符串长度是不包括最后的‘\0’

while(1){
        // 写数据
        // 接收键盘输入
        char buf[512];
        fgets(buf, sizeof(buf), stdin);
        // 发送给服务器
        write(fd, buf, strlen(buf) + 1);
        // 接收服务器端的数据
        int len = read(fd, buf, sizeof(buf));
        printf("read buf = %s, len = %d\n", buf, len);
    }
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>

int main(int argc, const char* argv[]){
    // 创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1){
        perror("socker error");
        exit(1);
    }

    // 连接服务器
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(9999); // 在服务器进程中设置的端口?
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
    int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1){
        perror("connect error");
        exit(1);
    }
    // 通信
    while(1){
        // 写数据
        // 接收键盘输入
        char buf[512];
        fgets(buf, sizeof(buf), stdin);
        // 发送给服务器
        write(fd, buf, strlen(buf) + 1);
        // 接收服务器端的数据
        int len = read(fd, buf, sizeof(buf));
        printf("read buf = %s, len = %d\n", buf, len);
    }
    return 0;
}