C++网络编程——socket技术基础

研究生阶段项目开发用到了socket技术,写个博客简单记录一下socket通信相关的基础知识,包含我个人对socket技术的一些理解,个人经验,如有错误烦请大佬们批评指正

什么是socket

  中文翻译过来叫“套接字”,可以理解为一个通信端点,我们都知道主机与主机之间通信是通过ip和端口(传输层和网络层),那么两台主机上的应用程序(应用层)如果想相互交流,也需要借助主机间的通信机制,但是应用程序不是主机本身,想要使用这一机制,就需要借助于socket,也就是说,socket是连接应用层和各种网络协议的接口。
  socket的通信机制有点类似于我们用手机通信,当两个人需要远程对话的时候,首先要各有一部手机,然后需要插入电话卡(类似于ip地址和端口号),然后一方拨打另一方的手机号码,接通之后,可以相互发送/接收信息。

socket的类型

流格式套接字

  流格式套接字(Stream Sockets)也叫面向连接的套接字,代码中使用SOCK_STREAM表示。提到面向连接,一定是基于TCP协议的,SOCK_STREAM是一种可靠的、双向的通信数据流,在底层实现了重传机制,使得数据可以准确无误地到达另一台计算机。
  SOCK_STREAM的特征:

  • 数据按照顺序传输(早发送的早到)
  • 在传输过程中数据不会丢失(TCP协议保证)
  • 数据发送和接收是不同步的(传输的数据存入缓冲区)
    实际应用:HTTP协议传输数据

数据报格式套接字

  数据报格式套接字(Datagram Sockets),也叫无连接的套接字(听名字就知道基于UDP),代码中使用SOCK_DGRAM表示。拥有UDP协议传输的优缺点,数据传送速度快,消耗小,但是数据容易丢失或损坏
  SOCK_DGRAM的特征:

  • 快速传输(不注重顺序)
  • 数据可能会丢失或损坏(只是小概率事件,只是相对TCP来说不稳定)
  • 数据的发送和接收是同步的
  • 每次传输的数据大小有限制
    实际应用: QQ视频、语音

原始套接字

  原始套接字(Raw Socket)是一个特殊的额套接字类型,代码中使用SOCK_RAW表示,原始套接字的特殊之处在于:能够实现从应用层到数据链路层的所有数据操作。TCP/UDP类型的套接字只能访问传输层以及传输层以上的数据(因为TCP/UDP是传输层协议,当传到传输层的时候,下层的IP包头/帧头帧尾都已经被丢弃)
  关于原始套接字,在此仅仅作一个简单介绍,我了解和使用的不是很多,不敢妄言,感兴趣的同学可以自行百度。

C/S架构下的socket设计

  我参与开发的项目主要是C/S架构(网上的大多数socket教程也都采用C/S架构举例),就从这一方向简单介绍一下。
  下图是我自己绘制的Stream Sockets在客户端和服务器端上的建立创建、连接、通信和关闭流程图,并包含了通过程中各种状态的变化。在这里插入图片描述
  Datagram Sockets的流程与Stream Sockets流程类似,只是其中的TCP协议换成了UDP协议,少了几次握手和挥手的过程。

socket常用数据结构

socket描述符

int类型,用于保存创建好的套接字

sockaddr

struct sockaddr {
   
  unsigned short sa_family; /* 地址家族, AF_xxx */
  char sa_data[14]; /*14字节协议地址*/
};

其中sa_family是两字节的地址家族,一般都是“AF_xxx”的形式,用于指定函数返回的地址信息类型
  AF_INET:返回IPV4地址信息
  AF_INET6:返回IPV6地址信息
  AF_UNSPEC:可以返回任何协议族的地址
我们使用的基本都是第一种。
sa_data包含套接字中的目标地址和端口信息

sockaddr_in

struct sockaddr_in {
   
  short int sin_family; /* 通信类型 */
  unsigned short int sin_port; /* 端口 */
  struct in_addr sin_addr; /* Internet 地址 */
  unsigned char sin_zero[8]; /* 与sockaddr结构的长度相同*/
};

sin_family:等同于sa_family,实际应用中大多选择AF_INET
sin_port:存储端口号
sin_addr:存储IP地址,使用in_addr这个数据就够,并且要使用网络字节序
sin_zero:是为了让sockaddr与sockaddr_in两个结构体保持大小相同而保留的空字节
设计sin_zero,目的就是能让这两个结构体大小相等,进而能够相互转换,实际编程中多数使用第二个结构体来设置和获取地址信息,而作为参数传递时通常转换成sockaddr结构

in_addr

typedef struct in_addr {
   
  union {
   
  struct{
    unsigned char s_b1,s_b2, s_b3,s_b4;} S_un_b;
  struct{
    unsigned short s_w1, s_w2;} S_un_w;
  unsigned long S_addr;
  } S_un;
  } IN_ADDR;

是一个存储ip地址的共用体,有三种表达方式
第一种用四个字节表示IP地址的四个数字。
第二种用两个双字节表示IP地址
第三种用一个长整型来表示IP地址
给in_addr赋值最简单的方式就是inet_addr()函数,可以将一个代表IP地址的字符串转换为in_addr类型,其反函数是inet_ntoa()。

sockaddr_in ina;
ina.sin_addr.s_addr=inet_addr("192.168.0.1");

实际使用时需要对inet_addr()的返回值进行检查,如果为-1则说明函数错误,如果不检查无符号的-1则与广播地址255.255.255.255相同

网络与主机字节序的相互转换

需要先介绍一下网络字节序和主机字节序

网络字节序

是TCP/IP中已经规定好的一种数据表示格式,是一种固定格式,保证数据在不同主机之间传输时能够被正确解释。网络字节序采用大端存储方式(低位字节放在内存高位地址,高位字节放在内存低位地址)

主机字节序

主机字节序是多样性的,其存储方式取决于CPU等
判断主机字节序的方法

bool am_little_endian()
{
   
	unsigned short i=1;
	return (int)*((char*)(&i))?true:false;	//返回true则为小端存储
}

转换方法

htons()——主机字节序转换为网络字节序(short类型,两个字节)
htonl()——主机字节序转换为网络字节序(long类型,四个字节)
ntohs()——网络字节序转换为主机字节序(short类型)
ntohl()——网络字节序转换为主机字节序(long类型)
为了程序的可移植性,当数据需要被传输到网络上时,一定要判断主机字节序,并确保其和网络字节序相同
如sockaddr_in结构体中的sin_port和sin_addr就都需要确保为网络字节序

socket相关函数

终于写到这里了。。。以下有些函数是客户端独有的,有些函数是服务器端独有的,为了行文方便,我就混着写了,读者可以参考前面的图示来确定这些函数执行的先后顺序

socket()

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domian,int type,int protocol);

一个一个参数来看
domain:需要设置为AF_INET,等同于sa_family和sin_family。
type:告诉内核,我们使用的socket是SOCK_STREAM还是SOCK_DGRAM类型(一般用不到SOCK_RAW类型)。
protocol:指使用的协议,设置为0时,是默认跟随type参数来选择协议,如果type参数值为SOCK_STREAM,protocol默认为IPPROTO_TCP;如果type值为SOCK_DGRAM,protocol默认为IPPROTO_UDP。
这个函数返回一个int类型套接字,我们后面要用这个套接字实现客户端和服务器的连接

bind()

#include <sys/types.h>
#include <sys/socket.h>
int 
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
很高兴能够回答你的问题。以下是一个简单的即时通信系统的C++网络编程示例,基于Linux平台使用socket库进行开发。 客户端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 连接服务器 int connect_result = connect(client_socket, (sockaddr *) &server_address, sizeof(server_address)); if (connect_result == -1) { cout << "连接服务器失败!" << endl; return -1; } // 发送消息 char message[1024] = {0}; while (true) { cin.getline(message, sizeof(message)); if (strlen(message) == 0) { continue; } send(client_socket, message, strlen(message), 0); memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); return 0; } ``` 服务器端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int server_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 绑定socket和地址 bind(server_socket, (sockaddr *) &server_address, sizeof(server_address)); // 监听客户端连接 listen(server_socket, 10); // 接受客户端连接 sockaddr_in client_address{}; socklen_t client_address_len = sizeof(client_address); int client_socket = accept(server_socket, (sockaddr *) &client_address, &client_address_len); // 接收消息 char message[1024] = {0}; while (true) { int recv_result = recv(client_socket, message, sizeof(message), 0); if (recv_result <= 0) { break; } cout << "收到消息:" << message << endl; memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); close(server_socket); return 0; } ``` 在上面的代码中,客户端和服务器端都使用了socket库中的函数来完成网络通信。客户端使用`socket`函数创建socket,使用`connect`函数连接服务器,并使用`send`函数发送消息。服务器端使用`socket`函数创建socket,使用`bind`函数将socket与服务器地址绑定,使用`listen`函数监听客户端连接,并使用`accept`函数接受客户端连接,最后使用`recv`函数接收客户端发送的消息。 当然,这只是一个简单的示例。在实际开发中,你需要考虑更多的细节,例如如何处理多个客户端连接、如何处理异常情况等等。 希望这个示例能够帮助你了解Linux下C++ socket网络编程的基本流程和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值