一、什么是socket网络通信?

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

二、套接字(socket)

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

流socket的类型是SOCK_STREAM,基于TCP协议,是一个有序可靠双向字节流的通道,传输数据不会丢失、重复或乱序到达,而且它还有出错后重传机制(就像两个人在打电话,聊天您一句我一句,有来有往,没听清楚就再说一次)。

数据报套接的类型是SOCK_DGRAM,基于UDP协议。不需要建立和维持连接,并且对发送的数据长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、重复或错乱到达,UDP不是一个可靠的协议,但是它没有拥塞控制,效率比较高。

三、简单的socket通信过程

四、客户/服务端模式

1、服务端流程

1)创建服务端的socket。

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

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

4)接受客户端的连接。

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

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

7)关闭socket,释放资源。

示例(server.cpp)

#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("接收:%s\n",buffer);strcpy(buffer,"ok");if (send(clientfd,buffer,strlen(buffer),0)<=0) break;   // 向客户端发送响应结果。printf("发送:%s\n",buffer);}// 第6步:关闭socket,释放资源。close(listenfd); close(clientfd);
}

2、客户端流程

1)创建客户端的socket。

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

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

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

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

示例(client.cpp)

#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("发送:%s\n",buffer);memset(buffer,0,sizeof(buffer));if (recv(sockfd,buffer,sizeof(buffer),0)<=0) break;    // 接收服务端返回的结果。printf("接收:%s\n",buffer);}// 第4步:关闭socket,释放资源。close(sockfd);
}

在运行程序之前,必须保证服务器的防火墙已经开通了网络访问策略

五、注解

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数量是有限的(默认是1024,具体可以通过ulimit -a命令查看),在程序退出之前必须关闭已打开的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"、"www.baidu.com"等。

返回值:如果成功,返回一个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。

七:注意事项

listen()、connect0和accept()函数

1)服务端在调用listen()之前,客户端不能向服务端发起连接请求。
2)服务端调用listen()函数后,服务端的socket开始监听客户端的连接。
3)客户端调用connect()函数向服务端发起连接请求。
4)在TCP底层,客户端和服务端握手后建立起通信通道,如果有多个客户端请求,在服务端就会形成一个已准备好的连接队列。
5)服务端调用accept()函数从队列中获取一个已准备好的连接,函数返回一个新的socket, 新的socket用于与客户端通信,listen的socket只负责监听客户端的连接请求。

本文参考c语言技术网的文章加以总结。

简单的socket通信实现相关推荐

  1. java 通信 教程_Java实现简单的socket通信教程

    今天学习了一下java如何实现socket通信,感觉难点反而是在io上,因为java对socket封装已经很完善了. 今天代码花了整个晚上调试,主要原因是io的flush问题和命令行下如何运行具有pa ...

  2. Android简单实现Socket通信,客户端连接服务器后,服务器向客户端发送文字数据

    案例实现的是简单的Socket通信,当客户端(Android客户端)连接到指定服务器以后,服务器向客户端发送一句话文字信息(你可以拓展其它的了) 先看一下服务端程序的实现吧 Server.java i ...

  3. 使用vb.net开发简单的socket通信

    使用vb.net开发简单的socket通信(环境是visual stdio 2019) 说明 参考文章 实现功能 效果展示 代码 服务端 客户端 总结 说明 之前用vb6编程时都是用的winsock控 ...

  4. 使用Petalinux实现ZYNQ的linux程序开发(实现简单的socket通信程序)

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 (一)实现功能及开发环境 一.实现功能 二.开发环境 (二)实现步骤 一.开发环境搭建 1.设置Ubuntu 和 Windo ...

  5. socket通信之最简单的socket通信

    http://blog.csdn.net/xluren/article/details/8043484#t15 套接字有三种类型 流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRA ...

  6. c语言实现简单的socket通信

    一.通讯示意图 二.函数详解 1.socket()函数 int socket(int domain, int type, int protocol); socket函数对应于普通文件的打开操作.普通文 ...

  7. windows:简单的socket通信

    目录 说明 效果展示 服务端代码 客户端代码 函数和结构体详情 说明 这个示例是写了一个服务端和一个客户端,客户端发数据,服务器接数据. 服务端地址:10.131.8.185              ...

  8. Unity3d简单的socket通信

    vs2010或其他创建C#工程 C#端代码一: using System; using System.Collections.Generic; using System.Linq; using Sys ...

  9. socket通信 _ 一个简单的群聊系统

    群聊系统要用到通信socket协议,在java中要用到两个类java.net.ServerSocket和 Java.net.Socket.ServerSocket用于创建服务器,而Socket用于创建 ...

最新文章

  1. HDU3791(二叉搜索树)
  2. simulink中mpc模块怎么使用_Controllogix系统中ILX34MBS485模块使用
  3. MVVM Light Toolkit使用指南
  4. 在Java中Vector和ArrayList的区别
  5. 关于Weex,你想了解的一切都在这里
  6. 【UE4游戏开发】安装UE4时报SU-PQR1603错误的解决方法
  7. java初学者笔记总结day7
  8. Python下json中文乱码解决办法
  9. Eclipse修改JSP新建模板
  10. 花木兰替父从军java_花木兰替父从军 木兰诗原文
  11. Qiyuan-python接小球游戏
  12. 五楼小黑屋深造之思想升华
  13. Jetson Xavier NX使用yolov5+deepsort实现CSI摄像头的目标跟踪
  14. matlab自带电机案例,MATLAB电机仿真精华50例,源代码
  15. 张帆:人类移民火星最大的问题是缺少适宜的大气
  16. 用Java控制结构分支结构判断某个年份是否为闰年并输出结果
  17. 深度学习 卷积神经网络原理
  18. 首批复星 BioNTech mRNA新冠疫苗抵达香港和澳门
  19. 21 届校招薪资曝光:严重倒挂老员工!
  20. 远程计算机上不接受445端口,怎么老提示445端口有人在连接?是不是中什么远程的毒?...

热门文章

  1. scenejs的一点Cameras小笔记
  2. windows 2008R2 无法安装操作系统补丁,或无法安装Sp1升级包的解决办法
  3. [网络安全自学篇] 六十四.Windows安全缺陷利用之SMBv3服务远程代码执行(CVE-2020-0796)复现及防御机理
  4. [知识图谱实战篇] 七.HTML+D3实现关系图谱搜索功能
  5. MFC VS2012对话框背景填图
  6. Jenkins 升级、迁移、备份
  7. 阿里云centos7安装和卸载图形化操作界面
  8. 信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言——1093:计算多项式的值
  9. ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 ——材料清单
  10. 大数据互联网架构阶段 Java爬虫