【Liunx Socket编程】——socket编程流程,以及创建一个简单的连接,实现服务器端向客户端发送数据

简要概述

创建流程

服务端

  1. 创建一个套接字 ——socket()
  2. 绑定IP地址、端口等信息到socket上——bind()
  3. 监听套接字——listen()
  4. 等待客户端的连接请求——accept()
  5. 发送、接收数据——send()和recv(),或者read()和write()
  6. 关闭网络连接——close()

客户端

  1. 创建一个套接字——socket()
  2. 连接服务器——connect()
  3. 接收、发送数据——send()和recv(),或者read()和write()
  4. 关闭网络连接——close()

代码分析

实现服务器端向客户端发送信息

以下socket编程用到的函数,可以点击查看具体的socket常用函数解析

服务器端

一、创建一个套接字——socket()

//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

二、绑定IP地址、端口等信息到socket上——bind()

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(1258);  //端口
// 将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

三、监听套接字——listen()

//进入监听状态,等待用户发起请求
listen(serv_sock, 20);

四、等待客户端的连接请求——accept()

//接收客户端请求
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

五、发送、接收数据——send()和recv(),或者read()和write()

//向客户端发送数据
char str[] = "Hello World!";
write(clnt_sock, str, sizeof(str));

六、关闭网络连接——close() 

//关闭套接字
close(clnt_sock);
close(serv_sock);

客户端

一、创建一个套接字——socket()

//创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);

二、连接服务器——connect()

先设置要连接的服务器的IP地址和端口等属性,然后连接

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
// 服务器端的信息
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(1258);  //端口
//向服务器(特定的IP和端口)发起请求
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

三、收发数据——send()和recv(),或者read()和write()

//读取服务器传回的数据
char buffer[40];
read(sock, buffer, sizeof(buffer)-1);

四、关闭网络连接——close()

//关闭套接字
close(sock);

完整代码

服务器端(service.c)

#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 serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        //将套接字和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(1258);  //端口
        bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        //进入监听状态,等待用户发起请求
        listen(serv_sock, 20);
        //接收客户端请求
        struct sockaddr_in clnt_addr;
        socklen_t clnt_addr_size = sizeof(clnt_addr);
        int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        //向客户端发送数据
        char str[] = "Hello World!";
        write(clnt_sock, str, sizeof(str));

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

客户端(client.c)

#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(1258);  //端口
        connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

        //读取服务器传回的数据
        char buffer[40];
        read(sock, buffer, sizeof(buffer)-1);

        printf("Message form server: %s\n", buffer);

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

运行

(需要开启两个终端)

先分别对两个文件进行编译(分别执行以下两条命令)

gcc service.c -o service
gcc client.c -o client

然后先运行服务器端

./service

 再在另一个终端中运行客户端

./client

执行完成

此时服务器端运行结束

客户端也运行结束,并打印出内容

Message form server: Hello World!

服务器端与客户端是全双工连接,即服务器端既可以发送数据也可以接收数据,客户端亦如此

socket编程中的多路复用是指通过一种机制,使一个进程可以监视多个文件描述符,一旦某个文件描述符就绪(一般是读写操作准备就绪),能够通知程序进行相应的读写操作。在Linux中,常用的多路复用机制有select、poll和epoll。其中,select是最古老的多路复用机制,poll是select的改进版,而epoll是最新、最高效的多路复用机制。多路复用机制可以大大提高程序的并发性能,使得程序可以同时处理多个客户端请求。 下面是一个简单的使用select实现多路复用的流程图和代码示例: 流程图: ``` 1. 创建socket并绑定端口 2. 将socket设置为非阻塞模式 3. 创建fd_set集合,并将socket加入集合 4. 进入循环,调用select函数,等待文件描述符就绪 5. 如果socket就绪,表示有新的客户端连接请求,调用accept函数接受连接 6. 如果其他文件描述符就绪,表示有客户端发送数据,调用recv函数接收数据并处理 7. 回到步骤4,继续等待文件描述符就绪 ``` 代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h> #define MAX_CLIENTS 10 #define BUFFER_SIZE 1024 int main(int argc, char *argv[]) { int server_fd, client_fd, max_fd, activity, i, valread, sd; struct sockaddr_in address; char buffer[BUFFER_SIZE] = {0}; fd_set readfds; // 创建socket并绑定端口 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(atoi(argv[1])); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } if (listen(server_fd, MAX_CLIENTS) < 0) { perror("listen failed"); exit(EXIT_FAILURE); } // 将socket设置为非阻塞模式 int flags = fcntl(server_fd, F_GETFL, 0); fcntl(server_fd, F_SETFL, flags | O_NONBLOCK); // 创建fd_set集合,并将socket加入集合 FD_ZERO(&readfds); FD_SET(server_fd, &readfds); max_fd = server_fd; // 进入循环,调用select函数,等待文件描述符就绪 while (1) { activity = select(max_fd + 1, &readfds, NULL, NULL, NULL); if (activity < 0) { perror("select error"); exit(EXIT_FAILURE); } // 如果socket就绪,表示有新的客户端连接请求,调用accept函数接受连接 if (FD_ISSET(server_fd, &readfds)) { if ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { perror("accept error"); exit(EXIT_FAILURE); } printf("New connection, socket fd is %d, ip is : %s, port : %d\n", client_fd, inet_ntoa(address.sin_addr), ntohs(address.sin_port)); // 将新的客户端socket加入集合 FD_SET(client_fd, &readfds); if (client_fd > max_fd) { max_fd = client_fd; } } // 如果其他文件描述符就绪,表示有客户端发送数据,调用recv函数接收数据并处理 for (i = server_fd + 1; i <= max_fd; i++) { sd = i; if (FD_ISSET(sd, &readfds)) { if ((valread = recv(sd, buffer, BUFFER_SIZE, 0)) == 0) { // 客户端关闭连接 printf("Client disconnected, socket fd is %d\n", sd); close(sd); FD_CLR(sd, &readfds); } else { // 处理客户端发送的数据 printf("Received message from client, socket fd is %d, message is %s\n", sd, buffer); memset(buffer, 0, BUFFER_SIZE); } } } } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值