在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程。该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端。

socket()函数

socket()函数用于创建一个套接字。这就好像购买了一个电话。不过该电话还没有分配号码。


  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int socket(int domain, int type, int protocol)

参数说明:

  • domain:指定通信的协议族,这些协议族定义在头文件< sys/socket.h >中。使用IPV4协议族时,该参数设置为AF_INET

  • type :指定socket的类型。在上一篇文章中介绍过,套接字常用的有三种类型:流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW

  • protocol : 该参数指定了一种协议类型用于所选择的套接字。如果仅有一种协议支持某种套接字类型,那么该参数可以定义为0,此时使用默认协议;如果一种套接字类型可能有多种协议类型,那么必须显式指定协议类型。关于具体细节,可以man socket进行查阅。

socket()的返回值:成功时返回非负整数;失败时返回-1;

bind() 函数

bind()函数绑定一个本地地址到套接字上,这相当于为电话绑定了号码。当一个套接字通过socket()被创建,它并没有绑定到具体的地址上,bind()来完成这个步骤。 bind()函数的函数原型如下:


  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int bind(int sockfd, const struct sockaddr *addr,
  4. socklen_t addrlen);

参数说明:

  • sockfd:socket()函数创建后成功返回的套接字

  • addr : 需要绑定的地址

  • addrlen:套接字的大小

这里需要使用到sockaddr_in结构来表示一个地址,该结构如下:


  1. struct sockaddr_in
  2. {
  3. sa_family_t sin_family;
  4. in_port_t sin_port;
  5. struct in_addr sin_addr;
  6. };
  7. struct in_addr
  8. {
  9. uint32_t s_addr;
  10. }

sockaddr_in需要强制转换为struct sockaddr*类型,传递给bind()函数的第二个参数。下面是一段例程:


  1. int main()
  2. {
  3. int listenfd = socket(AF_INET,SOCK_STREAM,0);
  4. if(listenfd == -1)
  5. err_exit("socket error");
  6. struct sockaddr_in addr;
  7. //填充结构
  8. addr.sin_family = AF_INET;
  9. addr.sin_port= htons(8001); //主机字节序转换为网络字节序
  10. addr.sin_addr= htonl(INADDR_ANY);//绑定主机的任一个IP地址
  11. /*下面两句具有相同的功能:都是绑定到本机ip地址*/
  12. //inet_aton("127.0.0.1",&addr.sin_addr);
  13. //addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  14. if(bind(listenfd,(const struct sockaddr*)&addr,sizeof(addr))==-1)
  15. err_exit("bind error");
  16. }

listen()函数

当使用socket()创建了一个套接字时,该套接字默认是主动套接字。使用listen()函数会使套接字称为一个被动套接字,也就是说,该套接字将被用来接受连接的数据,这些数据通过accept()函数接收。

listen()函数的函数原型如下:


  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int listen(int sockfd, int backlog);

参数说明:

  • sockfd : 套接字。

  • backlog: 指定连接队列的长度。

对于给定的监听套接字,内核需要维护两个队列:

  1. 已完成连接队列:该队列中的连接处于ESTABLISHED状态,也即是已经完成了三次握手过程。

  2. 未完成连接队列:该队列中的连接处于SYN_RCVD状态,还未建立连接。

两个队列的长度之和不能够超过backlogi。如果一个连接请求到达时未完成队列已满,客户端可能接收到一个错误指示ECONNREFUSED。服务器使用accept()函数从已完成连接队列的队头返回一个连接。下面是TCP为监听套接口维护的两个队列:

accept()函数

accept()函数用于从已完成队列的队头返回一个连接。它的函数原型为:


  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数说明:

  • sockfd : 服务器套接字

  • addr :用于接收对等方(客户端)的套接字地址。该参数填充为NULL时,不接收任何信息。

  • addrlen:返回对等方的套接字地址长度。如果不关心可以设置为NULL,否则一定要初始化。

函数返回值:成功返回一个非负整数,代表一个套接字;失败返回-1;

connect()函数

该函数用于建立一个连接到指定的套接字。函数的原型为:


  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int connect(int sockfd, const struct sockaddr *addr,
  4. socklen_t addrlen);

参数说明:

  • sockfd : 未连接的套接字

  • addr:未连接的套接字地址

  • addrlen:addr的长度

一个简单的socket 通信例程

客户端代码:


  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<errno.h>
  4. #include<arpa/inet.h>
  5. #include<netinet/in.h>
  6. #include<sys/types.h>
  7. #include<sys/socket.h>
  8. #include<string.h>
  9. #define ERR_EXIT(m)\
  10. do \
  11. {\
  12. perror(m);\
  13. exit(EXIT_FAILURE);\
  14. }while(0)
  15. int main()
  16. {
  17. /*创建一个套接字*/
  18. int sock = socket(AF_INET,SOCK_STREAM,0);
  19. if(sock == -1)
  20. ERR_EXIT("socket");
  21. /*定义一个地址结构*/
  22. struct sockaddr_in servaddr;
  23. memset(&servaddr,0,sizeof(servaddr));
  24. servaddr.sin_family = AF_INET;
  25. servaddr.sin_port = htons(5888);
  26. servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  27. /*进行连接*/
  28. if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
  29. {
  30. ERR_EXIT("connect");
  31. }
  32. else
  33. {
  34. printf("连接成功\n");
  35. }
  36. char sendbuf[1024]={0};
  37. char recvbuf[1024]={0};
  38. /*从标准输入中读入*/
  39. while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
  40. {
  41. write(sock ,sendbuf,strlen(sendbuf));
  42. if(read (sock,recvbuf,sizeof(recvbuf))>0)
  43. {
  44. printf("从服务器接收信息:\n");
  45. fputs(recvbuf,stdout);
  46. }
  47. memset(&sendbuf,0,sizeof(sendbuf));
  48. memset(&recvbuf,0,sizeof(recvbuf));
  49. }
  50. close(sock);
  51. return 0;
  52. }

服务器端代码:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<errno.h>
  4. #include<arpa/inet.h>
  5. #include<netinet/in.h>
  6. #include <sys/types.h>
  7. #include <sys/socket.h>
  8. #include<string.h>
  9. #define ERR_EXIT(m)\
  10. do \
  11. {\
  12. perror(m);\
  13. exit(EXIT_FAILURE);\
  14. }while(0)
  15. int main()
  16. {
  17. /* 创建一个套接字*/
  18. int listenfd= socket(AF_INET ,SOCK_STREAM,0);
  19. if(listenfd==-1)
  20. ERR_EXIT("socket");
  21. /*定义一个地址结构并填充*/
  22. struct sockaddr_in addr;
  23. addr.sin_family = AF_INET; //协议族为ipv4
  24. addr.sin_port = htons(5888); //绑定端口号
  25. addr.sin_addr.s_addr = htonl(INADDR_ANY);//主机字节序转为网络字节序
  26. /*将套接字绑定到地址上*/
  27. if(bind(listenfd,(const struct sockaddr *)&addr ,sizeof(addr))==-1)
  28. {
  29. ERR_EXIT("bind");
  30. }
  31. /*监听套接字,成为被动套接字*/
  32. if(listen(listenfd,SOMAXCONN)<0)
  33. {
  34. ERR_EXIT("Listen");
  35. }
  36. struct sockaddr_in peeraddr;
  37. socklen_t peerlen = sizeof(peeraddr);
  38. /*定义一个套接字,通常称为已连接套接字*/
  39. int conn ;
  40. conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
  41. if(conn <0)
  42. ERR_EXIT("accept error");
  43. else
  44. printf("连接到服务器的客户端的IP地址是:%s,端口号是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port));
  45. /*循环获取数据、发送数据*/
  46. char recvbuf[1024];
  47. while(1)
  48. {
  49. memset(recvbuf,0,sizeof(recvbuf));
  50. int ret = read(conn,recvbuf ,sizeof(recvbuf));
  51. fputs(recvbuf,stdout);
  52. write(conn,recvbuf,sizeof(recvbuf));
  53. }
  54. /*关闭套接字*/
  55. close(listenfd);
  56. close(conn);
  57. return 0;
  58. }

Socket编程实践(2) Socket API 与 简单例程的更多相关文章

  1. Socket编程实践(3) --Socket API

    socket函数 #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, ...

  2. Socket编程实践(2) --Socket编程导引

    什么是Socket? Socket可以看成是用户进程与内核网络协议栈的接口(编程接口, 如下图所示), 其不仅可以用于本机进程间通信,可以用于网络上不同主机的进程间通信, 甚至还可以用于异构系统之间的 ...

  3. C# socket编程实践

    C# socket编程实践——支持广播的简单socket服务器   在上篇博客简单理解socket写完之后我就希望写出一个websocket的服务器了,但是一路困难重重,还是从基础开始吧,先搞定C# ...

  4. Socket编程实践(6) --TCP服务端注意事项

    僵尸进程处理 1)通过忽略SIGCHLD信号,避免僵尸进程 在server端代码中添加 signal(SIGCHLD, SIG_IGN); 2)通过wait/waitpid方法,解决僵尸进程 sign ...

  5. Socket编程实践(6) --TCPNotes服务器

    僵尸进程过程 1)通过忽略SIGCHLD信号,避免僵尸进程 在server端代码中加入 signal(SIGCHLD, SIG_IGN); 2)通过wait/waitpid方法.解决僵尸进程 sign ...

  6. Socket编程实践(10) --select的限制与poll的使用

    select的限制 用select实现的并发服务器,能达到的并发数一般受两方面限制: 1)一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n(number)来调整或 ...

  7. C# socket编程实践——支持广播的简单socket服务器

    在上篇博客简单理解socket写完之后我就希望写出一个websocket的服务器了,但是一路困难重重,还是从基础开始吧,先搞定C# socket编程基本知识,写一个支持广播的简单server/clie ...

  8. Socket编程实践(1) 基本概念

    1. 什么是socket socket可以看成是用户进程与内核网络协议栈的编程接口.TCP/IP协议的底层部分已经被内核实现了,而应用层是用户需要实现的,这部分程序工作在用户空间.用户空间的程序需要通 ...

  9. C# Socket编程 笔记,Socket 详解,入门简单

    目录 一,网络基础 二,Socket 对象 三,Bind() 绑定与 Connect() 连接 四,Listen() 监听请求连接 和 Accept() 接收连接请求 五,Receive() 与 Se ...

随机推荐

  1. Socket编程——怎么实现一个服务器多个客户端之间的连接

      package coreBookSocket; import java.io.IOException; import java.net.ServerSocket; import java.net. ...

  2. MPAndroidChart 3.0——LineChart(折线图)

    显示效果 MPAndroidChart每一种图表的基本使用方式都基本相同 了解一种图表的实现 参考项目源码其他的图表也就差不多哩 在布局文件中定义 <com.github.mikephil.ch ...

  3. Highchart基础教程-图表配置

    一.图表容器: Highcharts 实例化中绑定容器的两种方式: 1.通过 dom 调用 highcharts() 函数的方式 $("#container").highchart ...

  4. SQL server学习

    慕课网sql server学习 数据库第一印象:desktop--web server--database server** 几大数据库:sql server.oracle database.DB2. ...

  5. Oracle的SQL基础

    1.了解SQL的种类 (1)DDL 数据定义语言:定义数据库中数据要如何存储的,包括对数据库对象的创建(create)修改(alter)删除(drop)的操作,这些对象主要有数据库,数据表,视图,索引 ...

  6. 办公OA的登陆界面..

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  7. [MySQL性能优化系列]巧用索引

    1. 普通青年的索引使用方式 假设我们有一个用户表 tb_user,内容如下: name age sex jack 22 男 rose 21 女 tom 20 男 ... ... ... 执行SQL语 ...

  8. 学习sql中的排列组合,在园子里搜着看于是。。。

    学习sql中的排列组合,在园子里搜着看,看到篇文章,于是自己(新手)用了最最原始的sql去写出来: --需求----B, C, F, M and S住在一座房子的不同楼层.--B 不住顶层.C 不住底 ...

  9. 【Linux】重定向与管道

    重定向 redirection 每个命令有输入源和输出目的地,默认行为,是标准输入和标准输出.大多数情况,标准输入是键盘,标准输出是屏幕.可以为单独的操作修改输入和输出,这就是重定向.重定向可以使某个 ...

  10. STM32C8T6 JTAG使用到PB3|PB4|PA13|PA14|PB15端口做普通IO时,需禁止JTAG!

    GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIO ...