socket 远程主机网络通信 ,详解 + 实例!!!

  目录

一、基本概念

二、原理

2.1 socket

2.2 bind

2.3 listen

2.4 accept

2.5 connect

2.6 read/recv

2.7 write/send

2.8 close 

三、实例

四、总结

五、参考文献


网络编程是 Linux 中非常重要的一块知识,不管是在工作还是面试中都会遇到,本篇文章将结合实例讲解 socket 网络通信,下面就开始吧!

一、基本概念

socket 通常称为套接字,是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。套接字为上层应用程序提供通信的接口,看下面这张图:

图1 网络层次结构                       

从上图可以看出,socket 实质上是一个抽象层,封装了下面的各种协议,方便了上层应用的调用。 

二、原理

服务端:

1. 通过 socket 函数创建套接字描述符;

2. 创建地址,使用 bind 将地址绑定到套接字描述符(此套接字称为监听套接字);

3. 然后,使用 listen 将套接字描述符设置为监听模式,并设置等待连接队列的长度;

4. 如果有连接,使用 accept 取出一个连接,与指定客户端建立连接,并新创建一个套接字用于与客户端通信,此套接字称为已连接套接字;

5. 使用 read/write 函数相互通信;

6. 所有通信完毕,使用 close 函数关闭套接字;

客户端:

1. 通过 socket 创建套接字;

2. 通过 connect 与指定服务器进行连接;

3. 连接成功后通过 read/write 进行通信;

4. 通信结束后通过 close 函数关闭套接字。

不同应用之间通信的过程如下所示:

图2 socket 网络通信

先介绍下上图中使用到的函数:

2.1 socket

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

该函数返回一个 int 类型的套接字,具有唯一性,可以与 open 函数产生的文件描述符类比。

2.2 bind

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

该函数将地址绑定到 socket 创建的套接上。

2.3 listen

int listen(int sockfd, int backlog);

该函数用于服务端,让服务端可以接收客户端(其它进程)的请求。

2.4 accept

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

 该函数从已完成连接的队列中取出一个连接,同时,返回一个新的(已连接)套接字,称为已连接套接字;而 socket 创建的套接字是监听套接字,监听套接字只有一个,而已连接套接字每一个 TCP 连接就生成一个。

2.5 connect

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

客户端通过 connect 函数与服务端进行连接,如果使用的 TCP 协议,那就是三次握手过程。

2.6 read/recv

ssize_t read(int fd,void *buf,size_t nbyte);
int recv(int sockfd,void *buf,int len,int flags);

 该函数负责从 fd/sockfd 中读取内容。

2.7 write/send

ssize_t write(int fd, const void*buf,size_t nbytes);
int send(int sockfd,void *buf,int len,int flags);

write/send 将 buf 中的数据写入文件描述符 fd/sockfd。

2.8 close 

int close(int sockfd);

关闭套接字。

以上函数成功返回0,失败返回-1,错误信息存放在 errno 中。

三、实例

服务端代码:

#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PORT 3322   // 服务器端口

#define BACKLOG 1
#define MAXRECVLEN 1024

int main(int argc, char *argv[])
{
    char buf[MAXRECVLEN];
    int listenfd, connectfd;   // listendfd 监听套接字  connectfd 已连接套接字
    struct sockaddr_in server; // 服务器地址信息
    struct sockaddr_in client; // 客户端地址信息
    socklen_t addrlen;
    
    // 创建套接字描述符
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket() error. Failed to initiate a socket");
        return 0;
    }
 
    int opt = SO_REUSEADDR;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);
    server.sin_addr.s_addr = htonl(INADDR_ANY);

    // 将地址 server 绑定到套接字 listenfd 上
    if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
        perror("Bind() error.");
        return 0;
    }
    
    // 将套接字设置为监听模式,队列长度为 1
    if(listen(listenfd, BACKLOG) == -1) {
        perror("listen() error. \n");
        return 0;
    }

    addrlen = sizeof(client);
    while(1) {
        // 取出一个客户端请求进行连接
        if((connectfd = accept(listenfd,(struct sockaddr *)&client, &addrlen)) == -1) {
            printf("accept() error. \n");
            break;
        }

        printf("The client's ip : %s, port %d\n",inet_ntoa(client.sin_addr),htons(client.sin_port));
        
        // 与客户端通信
        char str[] = "Hello Client, I'm Server!\n";
        while(1) {
            int iret = recv(connectfd, buf, MAXRECVLEN, 0);
            if(iret > 0) {
                printf("%s\n",buf);
            } else {
                close(connectfd);
                break;
            }
            send(connectfd, str, sizeof(str), 0); 
        }
    }
    // 关闭 socket
    close(listenfd); 
    return 0;
}

客户端代码:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define PORT 3322  //服务器端口

#define MAXDATASIZE 100

int main(int argc, char *argv[])
{
    int sockfd, num;
    char buf[MAXDATASIZE];
    struct hostent *host;
    struct sockaddr_in server;
    
    // 参数检查
    if (argc != 2) {
        printf("Usage: %s <IP Address>\n",argv[0]);
        return 0;
    }
    
    if((host = gethostbyname(argv[1])) == NULL) {
        printf("gethostbyname() error\n");
        return 0;
    }
    
    // 创建套接字
    if((sockfd = socket(AF_INET,SOCK_STREAM, 0))==-1) {
        printf("socket() error\n");
        return 0;
    }

    bzero(&server,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);
    server.sin_addr = *((struct in_addr *)host->h_addr);

    // 与服务器建立连接
    if(connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
        printf("connect() error\n");
        return 0;
    }
    
    // 与服务器进行通信
    char str[] = "Hello Server, I'm Client!\n";
    while(true) {
        if((num = send(sockfd,str,sizeof(str),0)) == -1) {
            printf("send() error\n");
            break;
        }

        if((num = recv(sockfd,buf,MAXDATASIZE,0)) == -1) {
            printf("recv() error\n");
            break;
        }
        buf[num-1]='\0';
        printf("%s\n", buf);
        sleep(3);
    }

    // 关闭套接字
    close(sockfd);
    return 0;
}

Makefile文件:

all: client server

client: client.c
	gcc -o client client.c

server: server.c 
	gcc -o server server.c

clean:
	rm -rf client server

编译运行后的结果,同一个主机通信:

图3 同一个机器通信

如果服务端和客户端都在本地,可以使用共享 socket 文件的方式通信,而不是通过绑定端口。 

不同主机通信:

图4 远程通信服务器端
图5 远程通信客户端

四、总结

上面更多的是从应用的角度讲解了如果通过 socket 进行网络通信,后面有时间将会从更深层次的角度讲解。

五、参考文献

[1] https://www.cnblogs.com/langren1992/p/5101380.html

[2] https://blog.csdn.net/tanzongbiao/article/details/82344074

[3] https://blog.csdn.net/wjy741223284/article/details/98513237

猜你喜欢

转载自blog.csdn.net/u011074149/article/details/113275450