Linux C TCP编程(socket,select/poll/epoll)

简介: 本文主要介绍了linux下标准的TCP通信流程,实现了客户端和服务器的通信,主要实现了消息的回发,即服务器将消息原封不动的回发给客户端。如果对接口不熟悉可以参考socket api介绍或者参考其他博客。

前言

本文主要介绍了linux下标准的TCP通信流程,实现了客户端和服务器的通信,主要实现了消息的回发,即服务器将消息原封不动的回发给客户端。如果对接口不熟悉可以参考socket api介绍或者参考其他博客。


客户端代码

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_PORT  9999  //服务器端口
#define SERVER_IP   "192.168.179.128" //服务端IP
#define MAXLEN  4096 //缓冲区最大大小
int main(){
   int socketfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
    servaddr.sin_port = htons(SERVER_PORT);
    int ret = connect(socketfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr_in));
    if(ret != 0){
        printf("connect server failed:error number:%d\n", errno);
        exit(-1);
    }
    while(1){
        const char *message = "this is from client message";
        ret = send(socketfd, message, strlen(message), 0);
        if(ret > 0){
            char recvbuf[MAXLEN] = {0}; 
            ret = recv(socketfd, recvbuf, MAXLEN, 0);
            if(ret > 0){
                printf("recv message from server:%s\n", recvbuf);
            }else if(ret == 0){
                printf("server has closed!\n");
                close(socketfd);
            } 
            else{
                printf("recv error :%d\n", errno);
                close(socketfd);
            }
        }
        sleep(1);
    }
    return 0;
}

服务端代码

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define MAXLNE  4096
int main(int argc, char **argv) 
{
    int listenfd, connfd, n;
    struct sockaddr_in servaddr;
    char buff[MAXLNE];
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    //listen接口之后会一直监听客户端的连接,每次客户端连接,都会和其创建连接(三次连接时内核完成的,不是由应用程序去控制的)
  //三次握手不发生在任何API中,协议栈本身被动完成的。
    if (listen(listenfd, 10) == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    //这个版本只能用于多个客户端连接,而只有第一个客户端可以发送数据
  //只accept第一个客户端的连接(也就是只会与第一个客户端进行通信)
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
        printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    printf("========waiting for client's request========\n");
    while (1) {
        //阻塞等待第一个客户端的数据的到来
        n = recv(connfd, buff, MAXLNE, 0);
        if (n > 0) {
            buff[n] = '\0';
            printf("recv msg from client: %s\n", buff);
      send(connfd, buff, n, 0);
        } else if (n == 0) {
            close(connfd);
        }
        //close(connfd);
    }
    close(listenfd);
    return 0;
}

代码思考

以上代码为什么可以支持多个客户端的连接,却只有第一个连接的客户端可以发送数据给服务器?

关于为什么支持多个客户端的连接,代码里已经有说明,如何实现多客户端给服务器发送数据呢,多线程或者I/O多路复用可以解决。


支持多客户端的服务端代码版本

一客户端一线程

这种方式是早期的网络服务器模型,不过目前由于客户端很多时候都是百万甚至千万级别的,所以这个只适用于支持少量客户端的情况,主要是因为服务器不可能支持太多线程的原因

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#define MAXLNE  4096
//一个线程8M,4G内存,最多512个线程(仅仅是线程就占用了4个G)
//C10k
void *client_routine(void *arg) { //
  int connfd = *(int *)arg;
  char buff[MAXLNE];
  while (1) {
  int n = recv(connfd, buff, MAXLNE, 0);
        if (n > 0) {
            buff[n] = '\0';
            printf("recv msg from client: %s\n", buff);
      send(connfd, buff, n, 0);
        } else if (n == 0) {
            close(connfd);
    break;
        }
  }
  return NULL;
}
int main(int argc, char **argv) 
{
    int listenfd, connfd, n;
    struct sockaddr_in servaddr;
    char buff[MAXLNE];
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    //listen接口之后会一直监听客户端的连接,每次客户端连接,都会和其创建连接(三次连接时内核完成的,不是由应用程序去控制的)
  //三次握手不发生在任何API中,协议栈本身被动完成的。
    if (listen(listenfd, 10) == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
  while (1) {
  struct sockaddr_in client;
     socklen_t len = sizeof(client);
     if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
         printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
         return 0;
     }
  //1个客户端1个线程去处理,早期的网络模型就是这种模式
  //缺点:不适用于当前高并发的情况。
  //优点:逻辑简单
  pthread_t threadid;
  pthread_create(&threadid, NULL, client_routine, (void*)&connfd);
    }
    close(listenfd);
    return 0;
}

I/O多路复用版本

select版本

poll版本

epoll版本


相关文章
|
1月前
|
网络协议 算法 Linux
【嵌入式软件工程师面经】Linux网络编程Socket
【嵌入式软件工程师面经】Linux网络编程Socket
48 1
|
10天前
|
SQL 自然语言处理 网络协议
【Linux开发实战指南】基于TCP、进程数据结构与SQL数据库:构建在线云词典系统(含注册、登录、查询、历史记录管理功能及源码分享)
TCP(Transmission Control Protocol)连接是互联网上最常用的一种面向连接、可靠的、基于字节流的传输层通信协议。建立TCP连接需要经过著名的“三次握手”过程: 1. SYN(同步序列编号):客户端发送一个SYN包给服务器,并进入SYN_SEND状态,等待服务器确认。 2. SYN-ACK:服务器收到SYN包后,回应一个SYN-ACK(SYN+ACKnowledgment)包,告诉客户端其接收到了请求,并同意建立连接,此时服务器进入SYN_RECV状态。 3. ACK(确认字符):客户端收到服务器的SYN-ACK包后,发送一个ACK包给服务器,确认收到了服务器的确
|
18天前
|
网络协议 Linux 网络安全
Linux配置SSH允许TCP转发
Linux配置SSH允许TCP转发
20 1
|
26天前
|
缓存 监控 网络协议
c++高级篇(二) ——Linux下IO多路复用之select模型
c++高级篇(二) ——Linux下IO多路复用之select模型
|
1月前
|
消息中间件 存储 监控
实战Linux I/O多路复用:借助epoll,单线程高效管理10,000+并发连接
本文介绍了如何使用Linux的I/O多路复用技术`epoll`来高效管理超过10,000个并发连接。`epoll`允许单线程监控大量文件描述符,显著提高了资源利用率。文章详细阐述了`epoll`的几个关键接口,包括`epoll_create`、`epoll_ctl`和`epoll_wait`,以及它们在处理并发连接中的作用。此外,还探讨了`epoll`在高并发TCP服务场景的应用,展示了如何通过`epoll`和线程/协程池来构建服务框架。
183 3
|
10天前
|
网络协议 Linux
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
|
10天前
|
Linux 网络安全 虚拟化
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
|
18天前
|
网络协议 算法 Linux
技术笔记:Linux学习:TCP粘包问题
技术笔记:Linux学习:TCP粘包问题
15 0
|
24天前
|
缓存 监控 Java
Java Socket编程最佳实践:优化客户端-服务器通信性能
【6月更文挑战第21天】Java Socket编程优化涉及识别性能瓶颈,如网络延迟和CPU计算。使用非阻塞I/O(NIO)和多路复用技术提升并发处理能力,减少线程上下文切换。缓存利用可减少I/O操作,异步I/O(AIO)进一步提高效率。持续监控系统性能是关键。通过实践这些策略,开发者能构建高效稳定的通信系统。
|
24天前
|
IDE Java 开发工具
从零开始学Java Socket编程:客户端与服务器通信实战
【6月更文挑战第21天】Java Socket编程教程带你从零开始构建简单的客户端-服务器通信。安装JDK后,在命令行分别运行`SimpleServer`和`SimpleClient`。服务器监听端口,接收并回显客户端消息;客户端连接服务器,发送“Hello, Server!”并显示服务器响应。这是网络通信基础,为更复杂的网络应用打下基础。开始你的Socket编程之旅吧!