目录

  • 流程概述
  • 服务器端代码实现
  • 客户端代码实现
  • 函数和结构讲解
    • sockaddr_in和sockaddr
    • socket : 创建一个socket连接
    • bind :绑定地址以及端口号问题

流程概述

客户端与服务器之间的网络通信基本原理如下所示,复杂一点的架构可能会添加消息中间件。
对于服务端,通信流程如下:

1、调用socket函数创建监听socket
2、调用bind函数将socket绑定到某个IP和端口号组成的二元组上
3、调用listen函数开启监听
4、当有客户端连接请求时,调用accept函数接受连接,产生一个新的socket(与客户端通信的socket)
5、基于新产生的socket调用send或recv函数开始与客户端进行数据交流
6、通信结束后,调用close函数关闭socket

对于客户端,通信流程如下:

1、调用socket函数创建客户端socket
2、调用connect函数尝试连接服务器
3、连接成功后调用send或recv函数与服务器进行数据交流
4、通信结束后,调用close函数关闭监听socket

服务器端代码实现

#include <iostream>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>using namespace std;
int main() {// 创建一个监听socketint listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd == -1) {cout << " create listen socket error " << endl;return -1;}// 初始化服务器地址struct sockaddr_in bindaddr;bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);bindaddr.sin_port = htons(3000);if (bind(listenfd, (struct sockaddr *)& bindaddr, sizeof(bindaddr)) == -1) {cout << "bind listen socket error" << endl;return -1;}// 启动监听if (listen(listenfd, SOMAXCONN) == -1) {cout << "listen error" << endl;return -1;}while (true) {// 创建一个临时的客户端socketstruct sockaddr_in clientaddr;socklen_t clientaddrlen = sizeof(clientaddr);// 接受客户端连接int clientfd = accept(listenfd, (struct sockaddr *)& clientaddr, &clientaddrlen);if (clientfd != -1) {char recvBuf[32] = {0};// 从客户端接受数据int ret = recv(clientfd, recvBuf, 32, 0);if (ret > 0) {cout << "recv data from cilent , data:" << recvBuf << endl;// 将接收到的数据原封不动地发给客户端ret = send(clientfd, recvBuf, strlen(recvBuf), 0);if (ret != strlen(recvBuf)) {cout << "send data error" << endl;} else {cout << "send data to client successfully, data " << recvBuf <<endl;}} else {cout << "recv data error" <<endl;}close(clientfd);}}// 关闭监听socketclose(listenfd);return 0;
}

客户端代码实现

#include <iostream>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 3000
#define SEND_DATA "helloworld"using namespace std;int main() {// 创建一个socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);if (clientfd == -1) {cout << " create client socket error " << endl;return -1;}// 连接服务器struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);serveraddr.sin_port = htons(SERVER_PORT);if (connect(clientfd, (struct sockaddr *)& serveraddr, sizeof(serveraddr)) == -1) {cout << "connect socket error" << endl;return -1;}// 向服务器发送数据int ret = send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);if (ret != strlen(SEND_DATA)) {cout << "send data error" << endl;return -1;} else {cout << "send data to client successfully, data " << SEND_DATA <<endl;}// 从服务器拉取数据char recvBuf[32] = {0};ret = recv(clientfd, recvBuf, 32, 0);if (ret > 0) {cout << "recv data to client successfully, data " << recvBuf <<endl;} else {cout << "recv data to client error" << endl;}// 关闭socketclose(clientfd);return 0;
}

函数和结构讲解

sockaddr_in和sockaddr

在讲解套接字编程函数之前,有必要对socket编程的两个不可或缺的结构体进行说明:sockaddrsockaddr_in
结构如下:

struct sockaddr{__SOCKADDR_COMMON (sa_); /* Common data: address family and length.  */char sa_data[14];     /* Address data.  */};/* Structure describing an Internet socket address.  */
struct sockaddr_in{__SOCKADDR_COMMON (sin_);in_port_t sin_port;         /* Port number.  */struct in_addr sin_addr;     /* Internet address.  *//* Pad to size of `struct sockaddr'.  */unsigned char sin_zero[sizeof (struct sockaddr)- __SOCKADDR_COMMON_SIZE- sizeof (in_port_t)- sizeof (struct in_addr)];};

由于历史的原因,套接字函数中(如connect,bind等)使用的参数类型大多是sockaddr类型的。而如今进行套接字编程的时候大都使用sockaddr_in进行套接字地址填充.因此,这就要求对这些函数进行调用的时候都必须要讲套接字地址结构指针进行类型强制转换,例如:

struct sockaddr_in serv;bind(sockfd,(struct sockaddr *)&serv,sizeof(serv));

socket : 创建一个socket连接

/* Create a new socket of type TYPE in domain DOMAIN, usingprotocol PROTOCOL.  If PROTOCOL is zero, one is chosen automatically.Returns a file descriptor for the new socket, or -1 for errors.  */
extern int socket (int __domain, int __type, int __protocol) __THROW;

向用户提供一个套接字,即套接口描述文件字,它是一个整数,如同文件描述符一样,是内核标识一个IO结构的索引。
一般传入参数是这样的:

int clientfd = socket(AF_INET, SOCK_STREAM, 0);

__domain:这个参数指定一个协议簇,也往往被称为协议域。系统存在许多可以的协议簇,常见有AF_INET──指定为IPv4协议,AF_INET6──指定为IPv6,AF_LOCAL──指定为UNIX 协议域。这里指网络层的协议
__type:这个参数指定一个套接口的类型,套接口可能的类型有:SOCK_STREAMSOCK_DGRAMSOCK_SEQPACKETSOCK_RAW等等,它们分别表明字节流、数据报、有序分组、原始套接口。
__protocol:指定相应的传输协议,也就是诸如TCP或UDP协议等等,系统针对每一个协议簇与类型提供了一个默认的协议,我们通过把protocol设置为0来使用这个默认的值。这里指传输层的协议
返回值:socket函数返回一个套接字,即套接口描述字。如果出现错误,它返回-1,并设置errno为相应的值,用户应该检测以判断出现什么错

返回值:成功则返回0,失败返回非0

bind :绑定地址以及端口号问题

在服务器端我们有这样一段代码:

// 初始化服务器地址
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bindaddr.sin_port = htons(3000);
if (bind(listenfd, (struct sockaddr *)& bindaddr, sizeof(bindaddr)) == -1) {cout << "bind listen socket error" << endl;return -1;
}

函数参数解释:

/* Give the socket FD the local address ADDR (which is LEN bytes long).  */
extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)

bind的地址我们使用了宏INADDR_ANY,这个宏定义如下:

/* Address to accept any incoming messages.  */
#define INADDR_ANY      ((in_addr_t) 0x00000000)

如果应用程序不关心bind绑定的IP,则可以使用这个宏,底层的协议栈服务会自动选择一个合适的IP地址,这样在多个网卡机器上选择IP地址会变得简单
如果只想在本机上进行访问,bind函数地址可以使用本地回环地址
如果只想被局域网的内部机器访问,那么bind函数地址可以使用局域网地址
如果希望被公网访问,那么bind函数地址可以使用INADDR_ANY or 0.0.0.0

网络通信的基本逻辑是客户端连接服务器,即从客户端的地址:端口 连接到 服务器的地址:端口上。
一般来说,服务器的端口号是固定的,而客户端的端口号是连接发起时操作系统随机分配的,并且不会分配被占用的端口。端口号是一个short类型的值,其范围为0~65535.
如果将bind函数中的端口号设置为0,那么操作系统会随机为程序分配一个可用的监听端口。一般来说,服务程序不会这么做,因为服务程序是要对外服务的,必须让客户端知道确切的IP地址和端口号。
在特殊的应用中,我们也可以在客户端程序以指定的端口号连接服务器,与普通的流程相比就是在创建socket与发起connect之间多了一次bind操作:

图1 不绑定 图2 绑定

其他相关函数可以到往期文章中查看:
socket编程常见函数使用方法

C++网络编程快速入门(一):TCP网络通信基本流程以及基础函数使用相关推荐

  1. C++网络编程快速入门(二):Linux下使用select演示简单服务端程序

    目录 select参数解释 select使用规范 select使用缺点 基本流程 实例代码 通信效果演示 往期文章 select参数解释 extern int select (int __nfds, ...

  2. C++网络编程快速入门(四):EPOLL模型使用

    目录 基本使用方法 step1:创建epollfd step2:将fd绑定到epollfd step3:调用epoll_wait检测事件 epoll_wait与poll.select区别所在 水平触发 ...

  3. 【Socket网络编程进阶与实战】------ Socket网络编程快速入门

    前言 本篇博客主要是分享,socket网络编程进阶与实践☞socket网络编程快速入门 一.聊一聊Socket 学习目标与收获

  4. pdf python 3.7编程快速入门 潘中强_无python基础,这些书籍可以帮您快速入门。

    利用Python进行数据分析> 定 价:119 元 作者:韦斯·麦金尼(Wes McKinney)著;徐敬一译 ISBN:9787111603702 出 版 社:机械工业出版社 学习Python ...

  5. C++网络编程快速入门(三):阻塞与非阻塞式调用网络通信函数

    目录 阻塞与非阻塞定义 send与recv connect 一些问题 为什么要将监听socket设置为非阻塞 阻塞与非阻塞定义 阻塞模式指的是当前某个函数执行效果未达预期,该函数会阻塞当前的执行线程, ...

  6. Winsock网络编程快速入门

     一.基本知识 1.Winsock,一种标准API,一种网络编程接口,用于两个或多个应用程序(或进程)之间通过网络进行数据通信.具有两个版本: Winsock 1: Windows CE平台支持. ...

  7. Windows下C语言网络编程快速入门

    C语言的学习,一般的方式是,先学C,然后是C++,最好还要有汇编语言和微机原理基础,然后才是Visual C++.这样的方式,对学习者来说,要花费很多时间和耐力.而在学校教学中,也没有时间深入学习Wi ...

  8. Java NIO 非阻塞网络编程快速入门

    NIO 非阻塞网络编程快速入门 案例: 编写一个 NIO 入门案例,实现服务器端和客户端之间的数据简单通讯(非阻塞) 目的:理解 NIO 非阻塞网络编程机制 import java.net.InetS ...

  9. 网络编程-java入门

    网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编程是比较复 ...

最新文章

  1. Kindeditor放置两个调用readonly错误
  2. linux线程(互斥锁、条件)
  3. javame_JavaME:Google静态地图API
  4. 贾跃亭的惩罚来了!就这??
  5. iPhone 12延期恐实锤:台积电5nm A14芯片将延期3个月
  6. python execute异步执行_封装了一个对mysql进行异步IO的小工具
  7. 通信感知一体化概述(IMT-2030 6G)
  8. ESD问题案例分析-智能手表为例
  9. Windows XP和Windows 7双系统安装说明和注意事项
  10. Angular最佳实践之$http-麻雀虽小 五脏俱全
  11. 无敌哥-创新设计思维
  12. python爬虫(抓取百度新闻列表)
  13. CSP:重庆八中宏帆初级中学校初一编程社C2024HF700寒假集训总结——Day1
  14. 苹果教你如何保持iPhone电池健康、延长电池寿命
  15. Maven(保姆级全详)新手入门
  16. 矩阵分析理论在实际工程中的应用_机器学习中的线性代数
  17. python获取时间日期今天明天昨天前天
  18. 拯救12亿低头族!热敷1次相当于3次理疗,5分钟活血止痛,颈椎病一戴见效!...
  19. 使用Hummingbird提升Rackspace Cloud Files的性能
  20. 一个自己开发的并应用在很多项目里的unity关卡编辑器

热门文章

  1. 我是如何成功准备VUE项目之前的开发环境?
  2. mysql、oracle知识点总结
  3. 漫谈:Java和Python现在都挺火,我应该怎么选?Java和Python优缺点比较。
  4. 计算机的发展阶段及特点与未来发展,计算机的发展历史及未来
  5. ajax修改按钮的html值,ajax - 在yii 1中使用ajax根据选定的单选按钮选项更新html字段值 - 堆栈内存溢出...
  6. linux动态库ppt,LINUX系统中动态链接库创建与使用补充_区块链白皮书代写|市场计划书项目PPT设计_Tbleg...
  7. PHP发送数据到指定方法,php通过header发送自定义数据方法_php技巧
  8. voinc vue实现级联选择
  9. JS对url进行编码和解码(三种方式区别)
  10. 层次和约束:项目中使用vuex的3条优化方案