基于socket套接字的UDP/TCP服务器【网络编程】

基于socket套接字基本函数,我们就可以实现一些简单的回显服务器,及基于UDP/TCP的服务器和客户端。

UDP回显服务器:

简单思路:

1.socket()创建socket套接口

2.bind() 绑定IP地址端口号

3.循环的从连接端口接收数据,并重新写入

//
// 服务器:
// 1.启动
// 2.进入死循环(事件循环)
//   a>从socket中读取请求(Request)
//   b>根据Request的内容计算生成Response
//   c>把Response响应写回到socket
//
//此处为实现方便,实现一个echo_server(回显服务器)
/

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

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

//绑定的IP地址和端口号     ./server 127.0.0.1 9090
int main(int argc, char* argv[]) {
  //0.校验命令行参数
  if(argc != 3)
  {
    printf("usage : ./server [ip] [port]\n");
    return 1;
  }
  //1.服务器的初始化
  //a>创建socket
  //AF_INET :ipv4协议  (udp) 面向数据报
  int fd = socket(AF_INET, SOCK_DGRAM, 0);
  if(fd < 0) 
  {
    perror("socket");
    return 1;
  }
  //b>绑定ip地址端口号
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;  //协议族
  addr.sin_addr.s_addr  = inet_addr(argv[1]); 
  addr.sin_port = htons(atoi(argv[2]));
  //点分十进制的字符串ip地址转换成了数字(网络字节序)                  
  int ret = bind(fd, (sockaddr*)&addr, sizeof(addr));
  if(ret < 0) 
  {
    perror("bind");
    close(fd);
    return 1;
  }

  printf("Server start...\n");
  //2.进入死循环
  while(1)
  { 
    //  a>从socket中读取请求(Request)
    char buf[1024] ={0};
    sockaddr_in peer;   //对端的Ip地址端口号
    socklen_t len = sizeof(peer);
    ssize_t read_size = recvfrom(fd,buf, sizeof(buf) - 1, 0, (sockaddr*)&peer, &len);
    if(read_size < 0)
    {
      perror("recvfrom");
      continue; //忽略当前的错误,防止服务器崩溃
    }
    buf[read_size] ='\0';

    //   b>根据Request的内容计算生成Response(因为是回显服务器,此步骤省略)
    //   inet_ntoa 将数字地址 转成点分十进制的字符串
    //   ntohs  网络字节序转主机字节序
    printf("[clien%s:%d] say: %s\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port), buf);

    //   c>把Response响应写回到socket
    sendto(fd, buf, strlen(buf), 0, (sockaddr*)&peer, sizeof(peer));//strlen防止把1024全部传输,可能会造成无效数据的传递,效率低
    
  }
  close(fd);  //关闭文件描述符
  return 0;
}

UDP客户端:

简单思路:

1.socket()创建套接口

2.初始化服务器端口

3.循环的从标准输入读入数据写入服务器端口,然后尝试从服务器端口读

//
// 客户端:
// 1.用户输入数据;从标准输入输入字符串
// 2.把这个字符串发送给服务器 (请求 Request)
// 3.从服务器读取返回结果     (响应Response)
// 4.将返回结果打印到标准输出  
/
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

// ./client [server_ip] [server_port]
int main(int argc, char* argv[]) {
  if(argc != 3)
  {
    printf("Usage ./client [ip], [port]\n");
    return 1;
  }
  int fd = socket(AF_INET, SOCK_DGRAM, 0);
  if(fd < 0)
  {
    perror("socket");
    return 1;
  }
  sockaddr_in server_addr;    //服务器端的ip地址和端口号
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = inet_addr(argv[1]);
  server_addr.sin_port = htons(atoi(argv[2]));
  while(1)
  { 
    //1.读取数据
    char buf[1024]= {0};
    ssize_t read_size = read(0, buf, sizeof(buf) - 1);
    if(read_size < 0)
    {
      perror("read");
      return 1; //客户端根据需要选择出错程序终止或者忽略出错
    }
    if(read_size == 0)
    {
      printf("read finish\n");
      return 0;
    }
    //2.发送数据到服务器
    sendto(fd, buf, strlen(buf), 0, (sockaddr*)&server_addr, sizeof(server_addr));

    //3.尝试从服务器读取响应
    //recvfrom 第五个第六个参数表示对端的Ip地址端口号,
    //此时由于客户端收到的数据一定是服务器端返回的相应数据
    //所以此时就可以忽略掉对端的ip地址和端口号(NULL)
    char buf_response[1024] = {0};
    read_size = recvfrom(fd, buf_response, sizeof(buf_response) - 1, 
        0, NULL, NULL);  
    if(read_size < 0)
    {
      perror("recvfrom");
      return 1;
    }
    buf_response[read_size] = '\0';
    //4.把响应写到标准输出
    printf("server response: %s\n", buf_response);
  }
  close(fd);
  return 0;
}

TCP服务器:

多进程版:

///
//  服务器基本流程:
//  1.从socket中读取数据(request)
//  2.根据Request 计算生成Response
//  3.把Response写回客户端
//  由于是回显服务器、计算生成response步骤省略
/

//多进程版本

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

//当前需要改进为多进程版本
//每个链接需要创建一个子进程来处理请求

void ProcessGrandChild(int new_sock, sockaddr_in* client_addr)
{
  //a>从客户端读取数据
  while(1)
  {
    char buf[1024] = {0};
    ssize_t read_size = read(new_sock, buf, sizeof(buf) - 1);
    if(read_size < 0)
    {
      perror("read");
      continue;
    }
    if(read_size == 0)
    {
      //TCP中,如果read的返回值为0, 说明对端关闭了连接
      printf("[client %s] disconnect!\n", inet_ntoa(client_addr->sin_addr));
      close(new_sock);
      return;
    }
    buf[read_size] = '\0';
    //b> 根据请计算响应(省略)
    printf("[client %s] %s\n", inet_ntoa(client_addr->sin_addr), buf);

    //把相应结果写回到客户端
    write(new_sock, buf, strlen(buf));
  }
  return;
}

void ProcessConnect(int new_sock, sockaddr_in* client_addr) {
  //创建子进程
  pid_t pid =fork();
  if(pid < 0)
  {
    perror("fork");
    return;
  }
  else if(pid == 0)  //子进程
  {
    if(fork() == 0) //创建孙子进程来处理,孙子进程返回由一号进程负责回收
    {
      ProcessGrandChild(new_sock, client_addr);
    }
    exit(0);
  }
  else
  {
    close(new_sock);
    waitpid(pid, NULL, 0);
  }
    
}

int main(int argc, char* argv[]) 
{
  if(argc != 3)
  {
    printf("Usage : ./tcp_server [ip] [prot]\n");
    return 1;
  }
  //1.创建socket
  //                                面向字节流--tcp
  int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
  if(listen_sock < 0)
  {
    perror("socket");
    return 1;
  }
  //2.绑定端口号

  sockaddr_in addr;
  addr.sin_family      = AF_INET;
  addr.sin_addr.s_addr = inet_addr(argv[1]);
  addr.sin_port        = htons(atoi(argv[2])); 

  int ret = bind(listen_sock, (sockaddr*)&addr, sizeof(addr));
  if(ret < 0)
  {
    perror("bind");
    close(listen_sock);
    return 1;
  }
  //3. 使用listen允许服务器被客户端链接  --- 转换成被动模式
  ret = listen(listen_sock, 5);
  if(ret < 0)
  {
    perror("listen");
    close(listen_sock);
    return 1;
  }
  //4. 服务器初始化完成,进入时间循环
  // TCP的链接和管理是有内核管理的
  
  printf("Tcp_server Init Ok\n");
  
  while(1)    
  {
    //把内核中建立好的链接放到用户代码空间中处理
    sockaddr_in peer;
    socklen_t len = sizeof(peer);
    int new_sock = accept(listen_sock, (sockaddr*)&peer, &len); //获取客户端
                                                    //链接后的到的新的socket
    if(new_sock < 0)
    {
      perror("accept");
      continue;
    }
    printf("[client %s] connect\n", inet_ntoa(peer.sin_addr));
    ProcessConnect(new_sock, &peer);    //处理链接
  }

  return 0;
}

多线程版:

///
//  服务器基本流程:
//  1.从socket中读取数据(request)
//  2.根据Request 计算生成Response
//  3.把Response写回客户端
//  由于是回显服务器、计算生成response步骤省略
/

//多线程版本

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

//当前需要改进为多线程版本

typedef struct Arg
{
  int fd;
  sockaddr_in addr;
}Arg;

void ProcessRequest(int client_fd, sockaddr_in* client_addr)
{
  char buf[1024] = {0};
  while(1)
  {
    ssize_t read_size = read(client_fd, buf, sizeof(buf));
    if(read_size < 0)
    {
      perror("read");
      continue;
    }
    if(read_size  == 0)
    {
      //对端关闭
      printf("Client %s disconnected..\n", inet_ntoa(client_addr->sin_addr));
      close(client_fd);
      break;
    }
    buf[read_size] = '\0';
    printf("Client %s say: %s\n", inet_ntoa(client_addr->sin_addr), buf);

    write(client_fd,buf, strlen(buf));
  }
    return;
}

void* EntryWork(void* arg)
{
  Arg* ptr = (Arg*)arg; 
  ProcessRequest(ptr->fd, &ptr->addr);

  free(ptr);
  return NULL;
}


// ./server 127.0.0.1 9090
int main(int argc, char* argv[]) 
{
  if(argc != 3)
  {
    printf("Usage: ./tcp_server [ip] [port]");
  }

  //创建套接字
  int sock = socket(AF_INET, SOCK_STREAM, 0);
  if(sock < 0)
  {
    perror("socket");
    return 1;
  }
  
  //绑定
  sockaddr_in addr;
  addr.sin_family      = AF_INET;
  addr.sin_addr.s_addr = inet_addr(argv[1]);
  addr.sin_port        = htons(atoi(argv[2]));;

  int ret = bind(sock, (sockaddr*)&addr, sizeof(addr));
  if(ret < 0)
  {
    perror("bind");
    close(sock);
    return 1;
  }
  //监听
  ret = listen(sock, 5);
  if(ret < 0)
  {
    perror("listen");
    close(sock);
    return 1;
  }

  printf("tcp_server start...\n");
  while(1)
  {
    sockaddr_in client_addr;
    socklen_t len = sizeof(client_addr);
    int client_fd = accept(sock, (sockaddr*)&client_addr, &len);
    if(client_fd < 0)
    {
      perror("accept");
      continue;
    }

    pthread_t tid;
    Arg* arg  = (Arg*)malloc(sizeof(Arg));
    arg->fd   = client_fd;
    arg->addr = client_addr;

    pthread_create(&tid, NULL, EntryWork, (void*)arg);
    pthread_detach(tid);

  }

  return 0;
}

TCP回显客户端:

///
//  服务器基本流程:
//  1.从标准输入读入字符串
//  2.把读入的字符串发送给服务器
//  3.尝试从服务器读取响应数据
//  4.把响应数据打印到标准输出
/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;


int main(int argc, char* argv[])
{
  if(argc != 3)
  {
    printf("Usage : ./client [ip] [port]\n");
    return 1;
  }

  //1.创建socket
  int fd = socket(AF_INET, SOCK_STREAM, 0);
  if(fd < 0)
  {
    perror("socket");
    return 1;
  }
  //2.建立连接
  sockaddr_in server_addr;
  server_addr.sin_family      = AF_INET;
  server_addr.sin_addr.s_addr = inet_addr(argv[1]);
  server_addr.sin_port        = htons(atoi(argv[2]));

  int ret = connect(fd, (sockaddr*)&server_addr, sizeof(server_addr));
  if(ret < 0)
  {
    perror("connect");
    close(fd);
    return 1;
  }
  //3.进入循环
  printf("connect success....\n");
  while(1)
  {
    // a>从标准输入读取数据
    printf("[Clent:#]");
    char buf[1024] = {0};
    ssize_t read_size = read(0, buf, sizeof(buf) - 1);
    if(read_size < 0)
    {
      perror("read");
      return 1;
    }
    if(read_size == 0)
    {
      printf("read done\n");
      return 0;
    }
    buf[read_size] = '\0';
    // b>把读入的数据发送到服务器上
    write(fd, buf, strlen(buf));    //这里也需要判定,但是,一般不会出错,本代码功能简单故而选择忽略
    // c>从服务器读取响应结果
    char buf_resp[1024] = {0};
    read_size = read(fd, buf_resp, sizeof(buf_resp) - 1);
    if(read_size < 0)
    {
      perror("read");
      close(fd);
      return 1;
    }
    if(read_size == 0)  //服务器断开链接
    {
      printf("server close socket\n");
      return 0;
    }
    buf_resp[read_size] = '\0';
    // d>把结果打印到标准输出
    printf("server response: %s\n", buf_resp); 
  }
  close(fd);
  return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值