当前位置: 首页 > article >正文

Linux下socket例子(c/c++)

Linux中的socket

    • 服务端逻辑
    • 客户端逻辑
    • c实现
    • c++实现
      • 服务端持续接收请求
      • 多线程实现
    • UDP实现

参考

服务端逻辑

  1. 创建service_sock
  2. 创建sockaddr_in结构体变量serv_addr,写明端口和ip
  3. 将socket和结构体绑定
  4. 监听socket
  5. 创建客户端sockaddr_in结构体变量clnt_addr
  6. 接收客户端请求,创建新的clnt_sock,客户端信息保存在clnt_addr中
  7. 向客户端发送数据
  8. 关闭clnt_sock
  9. 关闭serv_sock

客户端逻辑

  1. 创建sock
  2. 创建sockaddr_in结构体变量serv_addr,写明端口和ip
  3. 将socket和serv_addr作为参数,调用connect方法
  4. 调用read方法,读取服务端传来的数据
  5. 关闭sock

c实现

clien.c

#include <stdio.h>     //printf,sprintf,perror 相关声明在此文件中
#include <string.h>    //memset,strlen
#include <unistd.h>    //close
#include <arpa/inet.h> //sockaddr_in,socket,AF_INET,SOCK_STREAM,htons,inet_addr,connect,sockaddr,send,recv //相关定义和声明在此文件中

#define MAX_CONN 2
#define BUF_SIZE 1024
#define PORT 9000

int main(int argc, char *argv[])
{
  printf("argc is %d \n", argc);
 
	int i;
 
	for (i = 0; i<argc; i++)
	{
		printf("arcv[%d] is %s\n", i, argv[i]);
	}

  struct sockaddr_in server_sai;
  int sfd = 0, res = -1, recvbytes = 0, sendbytes = 0;
  char buf[BUF_SIZE], buf2[5] = {0}; // 进行变量的定义和初始化

  if (argc < 3) // 如果参数小于3个就报错,命令后面会分别加上IP地址和消息内容,所以一共是三个参数
  {
    printf("error number of argc:%d\n", argc);
    return res;
  }

  memset(buf, 0, sizeof(buf)); // 对buf清零
  sprintf(buf, "%s", argv[2]); // 将要传输的内容(第二个参数)复制到buf中

  if (-1 == (sfd = socket(AF_INET, SOCK_STREAM, 0))) // 创建一个IPV4的TCP socket
  {
    perror("socket");
    return res;
  }

  server_sai.sin_family = AF_INET;                                // IPV4 协议族
  server_sai.sin_port = htons(PORT);                              // 9000端口
  server_sai.sin_addr.s_addr = inet_addr(argv[1]);                // 使用第一个参数作为IP地址
  memset(&(server_sai.sin_zero), 0, sizeof(server_sai.sin_zero)); // 将结构体剩余部分填零

  if (-1 == connect(sfd, (struct sockaddr *)&server_sai, sizeof(struct sockaddr))) // 使用sfd进行连接
  {
    perror("connect");
    return res;
  }

  if (-1 == (sendbytes = send(sfd, buf, strlen(buf), 0))) // 将buf中的内容写到远端服务端,buf中的内容是命令行中的第二个参数
  {
    perror("send");
    return res;
  }

  recvbytes = recv(sfd, buf2, 5, 0);     // 从服务端接收数据,写到buf2中
  printf("%d -->%s\n", recvbytes, buf2); // 将buf2中的数据显示出来
  close(sfd);                            // 进行清理工作,关闭描述符

  res = 0;
  return res;
}

server.c

#include <stdio.h>      //perror,printf 相关函数在此声明
#include <netinet/in.h> //sockaddr_in,htons,htonl,socket,AF_INET,SOCK_STREAM,INADDR_ANY,SOL_SOCKET,SO_REUSEADDR,bind,listen,accept,recv,send 相关声明和定义在这个文件中
#include <string.h>     //memset 相关函数在此声明
#include <unistd.h>     //close 相关函数在此声明

#define MAX_CONN 2
#define BUF_SIZE 1024
#define PORT 9000

int main()
{
  struct sockaddr_in server_sai, client_sai;
  int sfd = 0, cfd = 0, res = -1, on = 1, recvbytes = 0, sendbytes = 0;
  int addrlen = sizeof(struct sockaddr);
  char buf[BUF_SIZE]; // 各种变量定义与初始化

  if (-1 == (sfd = socket(AF_INET, SOCK_STREAM, 0))) // 创建一个IPV4的TCP socket
  {
    perror("socket");
    return res;
  }

  server_sai.sin_family = AF_INET;                                // IPV4 协议族
  server_sai.sin_port = htons(PORT);                              // 9000端口
  server_sai.sin_addr.s_addr = htonl(INADDR_ANY);                 // 0.0.0.0 的通配监听
  memset(&(server_sai.sin_zero), 0, sizeof(server_sai.sin_zero)); // 将剩余部分填零
  setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));     // closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket

  if (-1 == bind(sfd, (struct sockaddr *)&server_sai, sizeof(struct sockaddr))) // 将 sfd 和 socket 地址进行绑定
  {
    perror("bind");
    return res;
  }

  if (-1 == (listen(sfd, MAX_CONN))) // 在sfd上进行监听,最多允许同时有2个请求在队列中排队,此配置正是DDOS的攻击点,协议天然的缺陷在于,不论这个值设多设少,都不会是一个适合的值
  {
    perror("listen");
    return res;
  }
  else
    printf("Listening...\n");

  if (-1 == (cfd = accept(sfd, (struct sockaddr *)&client_sai, (socklen_t *)&addrlen))) // 接受连接,将返回的描述符赋给cfd
  {
    perror("accept");
    return res;
  }

  memset(buf, 0, sizeof(buf)); // 将缓存置零

  if (-1 == (recvbytes = recv(cfd, buf, BUF_SIZE, 0))) // 从对端接受内容并且存到buf中
  {
    perror("recv");
    return res;
  }

  printf("Received a message:%s\n", buf); // 将收到的内容输出

  if (-1 == (sendbytes = send(cfd, "OK", 2, 0))) // 给客户端回复一个ok
  {
    perror("send");
    return res;
  }

  close(sfd);
  close(cfd); // 进行清理,关闭打开的描述符
  res = 0;
  return res;
}

c++实现

clien.cpp

#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);  //端口
    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;
}


server.cpp

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
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(1234);  //端口
    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);
    // 打印收到的客户端地址端口信息
    std::cout << "Client address: " << inet_ntoa(clnt_addr.sin_addr) << ", port: " << ntohs(clnt_addr.sin_port) << std::endl;

    //向客户端发送数据
    char str[] = "http://c.biancheng.net/socket/";
    write(clnt_sock, str, sizeof(str));
   
    //关闭套接字
    close(clnt_sock);
    close(serv_sock);
    return 0;
}

服务端持续接收请求

服务端持续接收客户端请求,其实就是加了个while循环

#include <arpa/inet.h>
#include <iostream>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.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(1234);                   //端口
  bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
  //进入监听状态,等待用户发起请求
  listen(serv_sock, 20);
  while (true) {
    //接收客户端请求
    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);
    // 打印收到的客户端地址端口信息
    std::cout << "Client address: " << inet_ntoa(clnt_addr.sin_addr)
              << ", port: " << ntohs(clnt_addr.sin_port) << std::endl;

    //向客户端发送数据
    char str[] = "http://c.biancheng.net/socket/";
    write(clnt_sock, str, sizeof(str));

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

多线程实现

client.cpp

#include <arpa/inet.h>
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <iostream>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
void *recvsocket(void *arg) //接受来自服务端数据的线程
{
  int st = *(int *)arg;
  char s[1024];
  while (1) {
    memset(s, 0, sizeof(s));
    int rc = recv(st, s, sizeof(s), 0);
    if (rc <= 0) //代表socket被关闭(0)或者出错(-1)
    {
      break;
    }
    printf("client receive:%s\n", s);
  }
  return NULL;
}
void *sendsocket(void *arg) //向服务端socket发送数据的线程
{
  int st = *(int *)arg;
  char s[1024];
  while (1) {
    memset(s, 0, sizeof(s));
    scanf("%s", s);
    int sc = send(st, s, strlen(s), 0);
  }
}
//执行 ./ClientLinux.out 127.0.0.1 8080
int main(int arg, char *args[]) {
  if (arg < 3) {
    printf("arg<3\n");
    return -1;
  }
  int port = atoi(args[2]);
  //第一步:初始化一个socket实例
  int st = socket(AF_INET, SOCK_STREAM, 0);

  //第二步:定义一个IP地址结构并设置值
  struct sockaddr_in addr;
  //内存初始化,将addr变量指向的内存签n个字节用0进行初始化填充
  memset(&addr, 0, sizeof(addr));
  //设置采用的协议为TCP/IP协议
  addr.sin_family = AF_INET;
  //设置端口号
  addr.sin_port = htons(port);
  //设置IP地址
  addr.sin_addr.s_addr = inet_addr(args[1]);

  //第三步:开始连接服务端
  if (connect(st, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
    printf("connect fail %s\n", strerror(errno));
    return EXIT_FAILURE;
  }

  //第四步:初始化要发送的信息并且通过send函数发送数据
  pthread_t thrd1, thrd2; //定义一个线程
  pthread_create(&thrd1, NULL, recvsocket, &st);
  pthread_create(&thrd2, NULL, sendsocket, &st);
  std::cout << "thrd1=" << thrd1 << std::endl;
  std::cout << "thrd2=" << thrd2 << std::endl;
  pthread_join(thrd1, NULL);
  pthread_join(thrd2, NULL);

  close(st);
  getchar();
  return EXIT_SUCCESS;
}

server.cpp

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include<arpa/inet.h>
#include <error.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
void* recvsocket(void* arg)//接受来着客户端数据的线程
{
    int st = *(int*)arg;
    char s[1024];
    while (1)
    {
        memset(s, 0, sizeof(s));
        int rc = recv(st, s, sizeof(s), 0);
        if (rc <= 0)//代表socket被关闭(0)或者出错(-1)
        {
            printf("sercver recv fail:%d\n", rc);
            break;
        }
        printf("server receive:%s\n", s);
    }
    return NULL;
}
void* sendsocket(void* arg)//向客户端socket发送数据的线程
{
    int st = *(int*)arg;
    char s[1024];
    while (1)
    {
        memset(s, 0, sizeof(s));
        scanf("%s", s);
        int sc = send(st, s, strlen(s), 0);
    }
}
//执行命令 ./ServerLinux.out 8080
int main(int arg, char* args[])
{
    if (arg < 2)
    {
        return -1;
    }
    int port = atoi(args[1]);
    int st = socket(AF_INET, SOCK_STREAM, 0);

    //setsockopt 设置socket的一个属性,让地址可以重用。
    int on = 0;
    if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
    {
        printf("setsockopt failed:%s\n", strerror(errno));
        return EXIT_FAILURE;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    //INADDR_ANY表示这个服务器上的所有Ip地址。一台服务器可以有多个ip地址。将socket绑定到这个机器的所有ip地址上
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    //将ip地址与server程序绑定
    if (bind(st, (struct sockaddr*) & addr, sizeof(addr)) == -1)
    {
        printf("bind fail %s\n", strerror(errno));
        return EXIT_FAILURE;
    }
    //server开始监听。 20代表同时有多少个连接过来(20并发)
    if (listen(st, 20) == -1)
    {
        printf("listen fail %s\n", strerror(errno));
        return EXIT_FAILURE;
    }
    //char s[1024];
    int client_st = 0;//客户端socket
    struct sockaddr_in client_addr;//客户端IP

    pthread_t thrd1, thrd2;//定义一个线程
    while (1)
    {
        memset(&client_addr, 0, sizeof(client_addr));
        socklen_t len = sizeof(client_addr);
        //accept会阻塞,直到有客户端连接过来。accept返回客户端的描述符
        client_st = accept(st, (struct sockaddr*) & client_addr, &len);
        if (client_st == -1)
        {
            printf("accept fail %s\n", strerror(errno));
            return EXIT_FAILURE;
        }
        //打印客户端的ip地址
        printf("accept ip : %s\n", inet_ntoa(client_addr.sin_addr));
        pthread_create(&thrd1, NULL, recvsocket, &client_st);
        pthread_create(&thrd2, NULL, sendsocket, &client_st);
    }
    close(st);
    getchar();
}

UDP实现

udpclient.cpp

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

#define BUF_SIZE 100
int main() {
  //创建套接字
  int sock = socket(PF_INET, SOCK_DGRAM, 0);
  //服务器地址信息
  struct sockaddr_in serv_addr;
  memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
  serv_addr.sin_family = PF_INET;
  serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  serv_addr.sin_port = htons(1234);
  //不断获取用户输入并发送给服务器,然后接受服务器数据
  struct sockaddr_in fromAddr;
  socklen_t addrLen = sizeof(fromAddr);
  while (1) {
    char buffer[BUF_SIZE] = {0};
    printf("Input a string: ");
    fgets(buffer, BUF_SIZE, stdin);
    sendto(sock, buffer, strlen(buffer), 0, (struct sockaddr *)&serv_addr,
           sizeof(serv_addr));
    int strLen = recvfrom(sock, buffer, BUF_SIZE, 0,
                          (struct sockaddr *)&fromAddr, &addrLen);
    buffer[strLen] = 0;
    printf("Message form server: %s\n", buffer);
  }
  close(sock);
  return 0;
}

udpserver.cpp

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

#define BUF_SIZE 100
int main() {
  //创建套接字
  int serv_sock = socket(AF_INET, SOCK_DGRAM, 0);
  //绑定套接字
  struct sockaddr_in serv_addr;
  memset(&serv_addr, 0, sizeof(serv_addr));      //每个字节都用0填充
  serv_addr.sin_family = PF_INET;                //使用IPv4地址
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP地址
  serv_addr.sin_port = htons(1234);              //端口
  bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
  //接收客户端请求
  struct sockaddr_in clnt_addr; //客户端地址信息
  socklen_t clnt_addr_size = sizeof(clnt_addr);
  char buffer[BUF_SIZE]; //缓冲区
  while (1) {
    int strLen = recvfrom(serv_sock, buffer, BUF_SIZE, 0,
                          (struct sockaddr *)&clnt_addr, &clnt_addr_size);

    //打印客户端的ip地址
    printf("accept ip : %s\n", inet_ntoa(clnt_addr.sin_addr));
    char message[] = "I am server !";
    sendto(serv_sock, message, strlen(message), 0, (struct sockaddr *)&clnt_addr,
           clnt_addr_size);
    // sendto(serv_sock, buffer, strLen, 0, (struct sockaddr *)&clnt_addr,
    //        clnt_addr_size);

  }
  close(serv_sock);
  return 0;
}

socket通信过程中,读/写和单线程/多线程中都会存在阻塞问题,可以根据输出验证。比如上面注释的一行sendto,反注释掉的话,会交替打印两个send信息。


http://www.kler.cn/news/137306.html

相关文章:

  • 【运维日常】lvm中的lv是如何扩容的---直接干
  • 网络渗透测试(TCP/IP)理论篇
  • [autojs]自动关闭青少年模式、个人信息保护指引弹窗
  • 第七讲(二):双指针,指针运用
  • Ubuntu20.04 安装微信 【wine方式安装】推荐
  • HTML5学习系列之响应式图像
  • python电影数据可视化分析系统的设计与实现【附源码】
  • JAVA小游戏 “拼图”
  • 开源集群管理系统对比分析:Kubernetes 与 Apache Mesos
  • Run Legends将健身运动游戏化,使用户保持健康并了解Web3游戏
  • 【Java系列】SpringBoot 集成MongoDB 详细介绍
  • OpenCV 卷积运算和卷积核
  • 参与活动如何进行地区的限制
  • 力扣刷题第二十七天--二叉树
  • 安卓老项目改造为AndroidX
  • php字符串处理函数的使用
  • CMake 判断操作系统类型
  • git基本操作(配图超详细讲解)
  • 交叉编译tcpdump
  • 游戏中的资源动态加载