socket报文_socket通信基础

2023-05-16

@[toc]

各位兄弟,在学习Linux编程基础之前,一定要先学习Linux基础知识和计算机网络基础知识,如果对这两方面的基础知识和基本概念不熟,谈不上Linux编程和网络通信编程。

一、socket通信的概念

socket也称作“套接字”,描述了计算机的IP地址和端口,运行在计算机中的程序之间采用socket进行数据通信。通信的两端都有socket,它是一个通道,数据在两个socket之间进行传输。

socket把复杂的TCP/IP协议族隐藏在socket接口后面,对程序员来说,只要用好socket相关的函数,就可以完成数据通信。

二、套接字(socket)

TCP提供了流(stream)和数据报(datagram)两种通信机制,所以套接字也分为流套接字和数据报套接字。

流套接字的类型是SOCK_STREAM,它提供的是一个有序、可靠、双向字节流的连接,因此发送的数据可以确保不会丢失、重复或乱序到达,而且它还有出错后重新发送的机制(就像两个人在打电话,聊天您一句我一句,有来有往,没听清楚就再说一次)。

数据报套接的类型是SOCK_DGRAM,它不需要建立和维持一个连接,采用UDP/IP协议实现。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高,因为它不需要建立和维持连接(就像一个人向另一个人发短信,一条短信发出去,对方不一定能收到)。

在实际开发中,数据报套接字(即UDP)的应用场景极少,本章节只介绍流套接字。

三、socket通信的过程

1)服务端程序将一个套接字绑定到指定的ip地址和端口,并通过此套接字等待和监听客户的连接请求。

2)客户程序向服务端程序绑定的地址和端口发出连接请求。

3)服务端接受连接请求。

4)客户端和服务端通过读写套接字进行通信。

四、客户/服务端模式 ===================

在TCP/IP网络应用中,两个程序之间通信模式是客户/服务端模式(client/server),客户/服务端也叫作客户/服务器,各人习惯。

服务端的工作流程

1)创建服务端的socket。

2)把服务端用于通信的地址和端口绑定到socket上。

3)把socket设置为监听模式。

4)接受客户端的连接。

5)与客户端通信,接收客户端发过来的报文后,回复处理结果。

6)不断的重复第5)步,直到客户端断开连接。

7)关闭socket,释放资源。

服务端示例(book242.cpp)

/*
 * 程序名:book242.cpp,此程序用于演示socket通信的服务端
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main()
{
  // 第1步:创建服务端的socket。
  int listenfd = socket(AF_INET,SOCK_STREAM,0);  

  // 第2步:把服务端用于通信的地址和端口绑定到socket上。
  struct sockaddr_in servaddr;    // 服务端地址信息的数据结构。
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;  // 协议族,在socket编程中只能是AF_INET。
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);          // 任意ip地址。
  //servaddr.sin_addr.s_addr = inet_addr("118.89.50.198"); // 指定ip地址。
  servaddr.sin_port = htons(5051);  // 指定通信端口。
  if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 )
  { perror("bind"); close(listenfd); return -1; }

  // 第3步:把socket设置为监听模式。
  if (listen(listenfd,5) != 0 ) { perror("listen"); close(listenfd); return -1; }

  // 第4步:接受客户端的连接。
  int  clientfd;                  // 客户端的socket。
  int  socklen=sizeof(struct sockaddr_in); // struct sockaddr_in的大小
  struct sockaddr_in clientaddr;  // 客户端的地址信息。
  clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
  printf("客户端(%s)已连接。n",inet_ntoa(clientaddr.sin_addr));

  // 第5步:与客户端通信,接收客户端发过来的报文后,回复ok。
  char buffer[1024];
  while (1)
  {
    memset(buffer,0,sizeof(buffer));
    if (recv(clientfd,buffer,sizeof(buffer),0)<=0) break;   // 接收客户端的请求报文。
    printf("接收:%sn",buffer);

    strcpy(buffer,"ok");
    if (send(clientfd,buffer,strlen(buffer),0)<=0) break;   // 向客户端发送响应结果。
    printf("发送:%sn",buffer);
  }

  // 第6步:关闭socket,释放资源。
  close(listenfd); close(clientfd);  
}

2、客户端的工作流程

1)创建客户端的socket。

2)向服务器发起连接请求。

3)与服务端通信,发送一个报文后等待回复,然后再发下一个报文。

4)不断的重复第3)步,直到全部的数据被发送完。

5)第4步:关闭socket,释放资源。

客户端示例(book241.cpp)

/*
 * 程序名:book241.cpp,此程序用于演示socket的客户端
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main()
{
  // 第1步:创建客户端的socket。
  int sockfd = socket(AF_INET,SOCK_STREAM,0); 

  // 第2步:向服务器发起连接请求。
  struct hostent* h; 
  if ( (h = gethostbyname("118.89.50.198")) == 0 )   // 指定服务端的ip地址。
  { perror("gethostbyname"); close(sockfd); return -1; }
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(5051); // 指定服务端的通信端口。
  memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
  if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)  // 向服务端发起连接清求。
  { perror("connect"); close(sockfd); return -1; }

  char buffer[1024];

  // 第3步:与服务端通信,发送一个报文后等待回复,然后再发下一个报文。
  for (int ii=0;ii<3;ii++)
  {
    memset(buffer,0,sizeof(buffer));
    sprintf(buffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1);
    if (send(sockfd,buffer,strlen(buffer),0)<=0) break;    // 向服务端发送请求报文。
    printf("发送:%sn",buffer);

    memset(buffer,0,sizeof(buffer));
    if (recv(sockfd,buffer,sizeof(buffer),0)<=0) break;    // 接收服务端返回的结果。
    printf("接收:%sn",buffer);
  }

  // 第4步:关闭socket,释放资源。
  close(sockfd);
}

在运行程序之前,必须保证服务器的防火墙已经开通了网络访问策略,如果您不明这句话的意思,说明您的Linux基础知识不够,请先学习Linux基础知识之后再来学习socket通信。

先启动服务端程序book242,服务端启动后,进入等待客户端连接状态,然后启动客户端。

客户端的输出如下:

服务端的输出如下:

五、注意事项 ============

1、别去纠缠细节

在socket通信的客户端和服务器的程序里,出现了多种数据结构,调用了多个函数,涉及到很多方面的知识,对初学者来说,更重要的是了解socket通信的过程、每段代码的用途和函数调用的功能,不要去纠缠这些结构体和函数的参数,这些函数和参数虽然比较多,但可以修改的非常少,别抄错就可以了,需要注意的地方我会提出。

2、服务端程序绑定地址

如果服务器有多个网卡,多个IP地址,socket通信可以指定用其中一个地址来进行通信,也可以任意ip地址。

1)指定ip地址的代码

m_servaddr.sin_addr.s_addr = inet_addr("192.168.149.129"); // 指定ip地址

2)任意ip地址的代码

m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  // 本主机的任意ip地址

在实际开发中,采用任意ip地址的方式比较多。

3、服务端程序绑定的通信端口

m_servaddr.sin_port = htons(5000);  // 通信端口

4、客户端程序指定服务端的ip地址

struct hostent* h; // ip地址信息的数据结构
if ( (h = gethostbyname("192.168.149.129")) == 0 )
{ perror("gethostbyname"); close(sockfd); return -1; }

5、客户端程序指定服务端的通信端口

servaddr.sin_port = htons(5000);

6、send函数

send函数用于把数据通过socket发送给对端。不论是客户端还是服务端,应用程序都用send函数来向TCP连接的另一端发送数据。

函数声明:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd为已建立好连接的socket。

buf为需要发送的数据的内存地址,可以是C语言基本数据类型变量的地址,也可以数组、结构体、字符串,内存中有什么就发送什么。

len需要发送的数据的长度,为buf中有效数据的长度。

flags填0, 其他数值意义不大。

函数返回已发送的字符数。出错时返回-1,错误信息errno被标记。

注意,就算是网络断开,或socket已被对端关闭,send函数不会立即报错,要过几秒才会报错。

如果send函数返回的错误(<=0),表示通信链路已不可用。

7、recv函数

recv函数用于接收对端socket发送过来的数据。

recv函数用于接收对端通过socket发送过来的数据。不论是客户端还是服务端,应用程序都用recv函数接收来自TCP连接的另一端发送过来数据。

函数声明:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd为已建立好连接的socket。

buf为用于接收数据的内存地址,可以是C语言基本数据类型变量的地址,也可以数组、结构体、字符串,只要是一块内存就行了。

len需要接收数据的长度,不能超过buf的大小,否则内存溢出。

flags填0, 其他数值意义不大。

如果socket的对端没有发送数据,recv函数就会等待,如果对端发送了数据,函数返回接收到的字符数。出错时返回-1,错误信息errno被标记。如果socket被对端关闭,返回值为0。

如果recv函数返回的错误(<=0),表示通信通道已不可用。

8、服务端有两个socket

对服务端来说,有两个socket,一个是用于监听的socket,还有一个就是客户端连接成功后,由accept函数创建的用于与客户端收发报文的socket。

9、程序退出时先关闭socket

socket是系统资源,操作系统打开的socket数量是有限的,在程序退出之前必须关闭已打开的socket,就像关闭文件指针一样,就像delete已分配的内存一样,极其重要。

值得注意的是,关闭socket的代码不能只在main函数的最后,那是程序运行的理想状态,还应该在main函数的每个return之前关闭。

六、相关的库函数

1、socket函数

socket函数用于创建一个新的socket,也就是向系统申请一个socket资源。socket函数用户客户端和服务端。

函数声明:

int socket(int domain, int type, int protocol);

参数说明:

domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

type:指定socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式socket(SOCK_STREAM)是一种面向连接的socket,针对于面向连接的TCP服务应用。数据报式socket(SOCK_DGRAM)是一种无连接的socket,对应于无连接的UDP服务应用。

protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

说了一大堆废话,第一个参数只能填AF_INET,第二个参数只能填SOCK_STREAM,第三个参数只能填0。

除非系统资料耗尽,socket函数一般不会返回失败。

2、gethostbyname函数

把ip地址或域名转换为hostent 结构体表达的地址。

函数声明:

struct hostent *gethostbyname(const char *name);

参数name,域名或者主机名,例如"192.168.1.3"、"http://www.freecplus.net"等。

返回值:如果成功,返回一个hostent结构指针,失败返回NULL。

gethostbyname只用于客户端。

gethostbyname只是把字符串的ip地址转换为结构体的ip地址,只要地址格式没错,一般不会返回错误。函数失败不会设置errno的值。

3、connect函数

向服务器发起连接请求。

函数声明:

int connect(int sockfd, struct sockaddr * serv_addr, int addrlen);

函数说明:connect函数用于将参数sockfd 的socket 连至参数serv_addr 指定的服务端,参数addrlen为sockaddr的结构长度。

返回值:成功则返回0, 失败返回-1, 错误原因存于errno 中。

connect函数只用于客户端。

如果服务端的地址错了,或端口错了,或服务端没有启动,connect一定会失败。

4、bind函数

服务端把用于通信的地址和端口绑定到socket上。

函数声明:

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数sockfd,需要绑定的socket。

参数addr,存放了服务端用于通信的地址和端口。

参数addrlen表示addr结构体的大小。

如果绑定的地址错误,或端口已被占用,bind函数一定会报错,否则一般不会返回错误。

5、listen函数

listen函数把主动连接套接字变为被动连接的套接字,使得这个socket可以接受其它socket的连接请求,从而成为一个服务端的socket。

函数声明:

int listen(int sockfd, int backlog);

返回:0-成功, -1-失败

参数sockfd是已经被bind过的套接字。socket函数返回的套接字是一个主动连接的套接字,在服务端的编程中,程序员希望这个套接字可以接受外来的连接请求,也就是被动等待客户端来连接。由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,程序员通过调用listen函数来完成这件事。

参数backlog,这个参数涉及到一些网络的细节,比较麻烦,填5、10都行,一般不超过30。

当调用listen之后,服务端的套接字就可以调用accept来接受客户端的连接请求。

listen函数一般不会返回错误。

6、accept函数

服务端接受客户端的连接。

函数声明:

int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

参数sockfd是已经被listen过的套接字。

参数addr用于存放客户端的地址信息,用sockaddr结构体表达,如果不需要客户端的地址,可以填0。

参数addrlen用于存放addr参数的长度,如果addr为0,addrlen也填0。

accept函数等待客户端的连接,如果没有客户端连上来,它就一直等待,这种方式称之为阻塞。

accept等待到客户端的连接后,创建一个新的套接字,函数返回值就是这个新的套接字,服务端使用这个新的套接字和客户端进行报文的收发。

accept在等待的过程中,如果被中断或其它的原因,函数返回-1,表示失败,如果失败,可以重新accept。

7、函数小结

服务端函数调用的流程是:socket->bind->listen->accept->recv/send->close

客户端函数调用的流程是:socket->connect->send/recv->close

其中send/recv可以进行多次交互。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

socket报文_socket通信基础 的相关文章

  • socket、tcp、udp、http 的认识及区别

    网络由下往上分为物理层 数据链路层 网络层 传输层 会话层 表示层和应用层 IP 协议对应于网络层 TCP协议对应于传输层 HTTP协议对应于应用层 三者从本质上来说没有可比性 socket则是对TCP IP协议的封装和应用 可以说 TPC
  • 读书笔记_《Linux高性能服务器编程》_第 5 章:网络编程基础API

    第 5 章 Linux网络编程基础API 知识要点 socket 地址 API socket 基础 API 网络信息 API 1 socket 地址API 主机字节序和网络字节序 CPU 32位 的累加器一次至少可以装载 4 字节 即一个整
  • Java图书管理系统 -- 基于Socket实现客户端服务端拆分

    图书管理系统小Demo又又又升级了 本图书管理系统已经经历了三个阶段 通过操作数组来实现图书的增删改查方法 用控制台获取用户输入来实现人机交互 通过集合容器存储对象 使用序列化在管理系统开启关闭时 加载 存储数据到本地 使用TCP协议实现客
  • 阿里云:网络编程 bind:cannot assign requested address errno:99 问题

    解决方案 阿里云上的服务器代码绑定的 IP 需要时内网 IP ifconfig 查看 其他客户端连接服务器时所用的 IP 得是阿里云的外网 IP 查看实例即可 分析思路 猜想1 bind cannot assign requested ad
  • Socket -- udp

    接收者 完成System out println UDPProvider Started 作为接收者 指定一个端口用于数据接收 DatagramSocket ds new DatagramSocket 20000 构建接收实体 final
  • Socket编程之聊天程序 - 模拟Fins/ModBus协议通信过程

    设备控制软件编程涉及到的基本通信方式主要有TCP IP与串口 用到的数据通信协议有Fins与ModBus 更高级别的通信如 net中的Remoting与WCF在进行C S架构软件开发时会采用 本篇文章结合Fins ModBus协议的指令帧结
  • c# 使用udp协议接收消息

    两个例子 例一 引用命名空间 using System Net using System Net Sockets 定义 private UdpClient Reveive IPAddress localIP IPAddress Parse
  • linux进程间通信---本地socket套接字(二)---多进程实现一个server对应多个client

    先给自己打个广告 本人的微信公众号正式上线了 搜索 张笑生的地盘 主要关注嵌入式软件开发 股票基金定投 足球等等 希望大家多多关注 有问题可以直接留言给我 一定尽心尽力回答大家的问题 想要获取完整源码的 关注公众号后回复 socket2 即
  • Unity3d之Socket UDP协议

    原文地址 http blog csdn net dingkun520wy article details 49201245 一 Socket 套接字 UDP协议的特点 1 是基于无连接的协议 没有生成连接的延迟所以速度比TCP快 2 支持一
  • 关于Socket编程中的inet_ntop、inet_pton和inet_ntoa、inet_addr

    VS2013中调试Socket代码时 遇到了点小问题 问题代码为 cpp view plain copy inet ntoa addrClient sin addr 生成错误消息为 plain view plain copy error C
  • Java实现一个简单的命令行聊天程序

    Socket编程 Java实现 客户端 package net import java io BufferedReader import java io InputStreamReader import java io PrintWrite
  • java socket——心跳包

    首先先说说心跳包在socket连接中的意义 通过socket连接的双方为了保证在一段时间未发消息不被防火墙断开连接或者使对方及时知道自己是否已经断线而定期给对方发送的某些特殊标识字符 这个字符可以根据双方自定义 没有实际的通讯意义 而定制的
  • Java Socket聊天室

    Socket聊天室 1 创建登录判断类UserLogin 2 创建登录服务器LoginServer 3 创建聊天服务器ChatServer 4 创建客户端Client 5 创建服务器用于处理聊天的线程类ChatThread 6 创建客户端C
  • Socket_Tcp加密文件传输系统

    代码下载链接 Socket Tcp 原文链接 https blog csdn net weixin 45746588 article details 107683901 这是一个课程的作业 我是初学者 所以写完这个代码还是花了我好几周的时间
  • 再谈TCP三次握手/四次挥手

    TCP三次握手 四次挥手 在TCP IP协议中 TCP协议提供可靠的连接服务 采用三次握手建立一个连接 如图1所示 1 第一次握手 建立连接时 客户端A发送SYN包 SYN j 到服务器B 并进入SYN SEND状态 等待服务器B确认 2
  • Windows平台下MingGW的网络socket编程模型

    Windows平台下MingGW的网络socket编程模型 1 TCP服务器 include
  • node socket 简易聊天室

    服务端 const net require net const server net createServer 用户列表 let clients 监听连接 server on connection client gt client on d
  • 解决前端websocket数据帧接收数据大小限制(数据分帧)问题

    websocket前后台出现问题解决方法 一开始通过限制后台返回数据帧以125字节分隔分段数据返回给前台 但调试时发现只要加上其他的一些信息返回json string很容易就会超过了125字节 于是在后台修改了这个限制大小为2048 但是这
  • SocketOutputStream和SocketChannel write方法的区别和底层实现

    Java直接内存原理提到了SocketChannel write的实现原理 通过IOUtil write将java堆内存拷贝到了直接内存 然后再把地址传给了I O函数 那么 BIO 是怎么实现往socket里面写数据的呢 BIO Socke
  • Socket编程中的强制关闭与优雅关闭及相关socket选项

    以下描述主要是针对windows平台下的TCP socket而言 首先需要区分一下关闭socket和关闭TCP连接的区别 关闭TCP连接是指TCP协议层的东西 就是两个TCP端之间交换了一些协议包 FIN RST等 具体的交换过程可以看TC

随机推荐