socket-地址族与数据序列

构建接电话套接字

一、套接字创建过程

1.调用socket函数(安装电话机)时进行的对话。成功时返回文件描述,失败时返回-1

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

 

2.调用bind函数(分配电话号码)时进行的对话,成功时返回0,失败时返回-1

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

 

3.调用listem函数(连接电话线)时进行的对话,成功时返回0,失败时返回-1

int listen(int sockfd,int backlog);

 

4.调用accept函数(拿起话筒)时进行的对话,成功时返回文件描述,失败时返回-1

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

 

 

网络编程中接受连接请求的套接字创建过程如下:

 1.调用socket函数创建套接字

 2.调用bind函数分配IP地址和端口号

 3.调用listen函数转为可接收请求状态

 4.调用accept函数受理连接请求

 

 

二、服务端创建的套接字又称服务端套接字或监听(listening)套接字,客户羰套接字的创建过程比创建服务器端

 套接字简单,客户端只有调用socket函数创建套接字和调用connect函数向服务器发送连接请求两个步骤:

 1.调用socket ,connect 

 2.与服务端共同运行以收发字符串数据

 

 打电话(请求连接)的函数,其调用的是客户端套接字,成功时返回0,失败时返回-1

 int connect(int sockefd,struct sockaddr *serv_addr,socklen_t addrlen);

 

 3.调用socket函数创建套接字,如果紧接着调用bind,listen将成为服务端套接字;如果调用connect

 将成为客户端套接字

 

        ||||

 

 4. GCC(GUN Complier Collection,GUN编译器集合)

 gcc server_socket.c -o hserver

 ./hserver 9190

 

 gcc client_socket.c -o hclient

 ./hclient 127.0.0.1 9190

 

 5.Linux而言,socket操作与文件操作没有区别,socket也被认为是文件的一种,因为在网络数据传输过程中自然可以使用文件I/O

 相关函数,而Windows则与Linux不同,是要区分socket和文件的

 

 6.如果想使用Linux提供的文件I/O函数,应该理解好文件描述符的概念:此处的文件描述符是系统分配给文件或套接字的整数,学习C

 语言过程中用过的标准输入输出及标准错误在Linux中也被分配如下的文件描述符:

  文件描述符        对象

    0               标准输入:Standard Input

    1               标准输出:Standard Output

    2               标准错误:Standard Error

 Windows平台中称"句柄",Linux平台则用描述符"


 三、文件操作

 1.打开文件

 int open(const char *path,int flag) 成功时返回文件描述,失败时返回-1

 

 第一个参数:文件路径

 第二个参数:文件打开模式

 

 打开模式          含义

 O_CREAT           必要时创建文件

 O_TRUNC           删除全部现有数据

 O_APPEND           维技现在数据,保存到其后面

 O_RDONLY           只读打开

 O_WRONLY           只写打开

 O_RDWR             读写打开

 

2.关闭文件

 int close(int fd) 成功时返回0,失败时返回-1

 fd:需要关闭的文件或套接字的文件描述符

 

3.将数据写入文件

 write函数用于向文件输出(传输)数据。Linux中不区分文件与套接字,因此,通过套接字向其他计算机传递数据

 时也会用到该函数

 ssize_t write(int fd,const void *buf,size_t nbytes);成功时返回写入的字节数,失败时返回-1

  fd:显示数据传输对象的文件描述符

  buf:保存要传输数据的缓冲地址值

  nbytes:要传输数据的字节数

 size_t通过typedef声明的unsigned int类型,ssize_tsize_t前面多加的s代表signed,ssize_t

 通过typedef声明的signed int类型

 4.读取文件中的数据

 ssize_t read(int fd,void *buf,size_t nbytes);成功时返回接收的字节数(但遇到文件结尾则返回0),失败时返回-1

 5. A:0-127 B:128-191 c:192-223

 6.计算机中一般配有NIC(Network Interface Card,网络接口卡)数据传输设备,通过NIC向计算机内部传输数据时会用到IP

 操作系统负责把传递到内部的数据适当分配给套接字,这时就要用到端口。也就是说,通过NIC接收的数据内有端口号,操作系统正是参

 此端口号把数据传输给相应端口的套接字

 6.1 端口号是在同一操作系统内为区分不同套接字而设置的,因此无法将我个端口号分配给不同套接字。另外,端口号由16位构成,可

 分配的端口号范围是0-65535,0-1023是知名端口(well-know PORT),一般分配给特定应用程序。所以应当分配此范围之外的值。

 另外,虽然端口号不能重复,但TCPUDP套接定不会共用端口号,所以允许复复

 7.IPv4地址的结构体

 struct sockaddr_in {

    sa_family_t     sin_family;     //地址族(Address Family)

    unit16_t        sin_port;       // 16TCP/UDP端口号

    struct in_addr  sin_addr;       // 32IP地址

    char            sin_zert[8];    //不使用

 };

 struct in_addr {

    In_addr_t  s_addr;              //  32IP地址

 }

 7.2

 sturct sockaddr_in serv_addr;

 if(bind(serv_sock,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) == -1)

 error_handling("bind() error");

 stutct sockaddr {

    sa_family_t       sin_family;   //地址族

    char                 sa_data[14];  //地址信息

 }

 此结构体成员sa_data保存的地址信息中需包含IP地址和端口号,剩余部分应填充0,这也是bind函数要求的。

 而这对于包含地址信息来讲非常麻烦,继而就有了新的结构体sockaddr_in.若按照之前的sockadr_in结构

 体,则将生成符合bind函数要求的字节流。最后转换为sockaddr型的结构体变量,再传递给bind函数即可。

 sockaddr_in是保存IPv4地址信息的结构体,还需要通过sin_family单独指定地址族信息,结构体sockaddr

 并非只为IPv4设计,这从保存地址信息的数组sa_data长度为14字节也可看出,因此,结构体sockaddr要求在

 sin_family中指定地址族信息,为了与sockaddr保持一致,sockaddr_in结构体中也有地址族信息

 POSIX(Portable Operation Systerm Interface,可移植操作系统接口).POSIX是为UNIX系列操作系统设立的标准,它定议

 了一些其他数据类型:

-------------------------------------------------------

 sys/types.h

-------------------------------------------------------

 int8_t         singned 8-bit int

 uint8_t        unsigned 8-bit int(unsigned char)

 int16_t        signed 16-bit int

 uint16_t       unsigned 16-bit int(unsigned short)

 int32_t        singned 32-bit int

 uint32_t       unsigned 32-bit int(unsigned long)

-------------------------------------------------------

 sys/socket.h

-------------------------------------------------------

 sa_family_t    地址族(address family)

 socklen_t      长度(length of struct)

 ------------------------------------------------------

 netinet/in.h

------------------------------------------------------

 in_addr_t      IP地址,声明为unit32_t

 in_port_t      端口号,声明为unit16_t

------------------------------------------------------

 7.3结构体sockaddr_in的成员分析

 sin_family        

 AF_INET        IPv4网络协议中使用的地址族

 AF_INET6       IPv6网络协议中使用的地址族

 AF_LOCAL       本地通信中采用的UNIX协议的地址族

 AF_LOCAL       只是为了说明具有多种地址族而添加的

 sin_port

 保存16位端口号,重点在于,它以网络字节序保存

 sin_addr

 保存32IP地址信息,且也以网络字节序保存

 sin_zero

 无特殊含义。只是为使结构体sockaddr_in的大小与sockaddr结构体保持一到而插入的成员。必需填充为0,

 否则无法得到想要的结果

7.4 网络字节序与地址变换,不同CPU中,4字节整数型值1在内存空间的保存方式是不同的

 00000000  00000000  00000000  00000001

 00000001  00000000  00000000  00000000

 字节序(Order)与网络字节序

 网络字节序:在通过网络传输数据时约定统一方式,这种约定称为网络字节序,非常简单--统一为大端序

 CPU向内存保存数据的方式有2种,这意味着CPU解析数据的方式也分为2

 大端序(Big Endian):高位字节存放到低位地址

 小端充(Little Endian):高位节节存放到高位地址

 大端序整数0x12345678中,0x12是最高位字节,0x78是最低位字节。因此大端序中先保存最高位字节0x12

 (最高位字节0x12存放低位地址)

 0x20       0x21        0x22        0x22

 0x12       0x34        0x56        0x78

  小端序整数0x12345678中,0x78是最低位字节

 0x20       0x21        0x22        0x22

 0x78       0x56        0x34        0x12

 从以上分析看出,每种CPU的数据保存方式均不同,因此代表CPU数据保存方式的主机字节序(Host Byte Order)

 在不同CPU中也各不相同,目前主流的Intel系列CPU以小端序方式保存数据

 字节序转换

 unsigned short htons(unsigned short); //short型数据从主机字节转化为网张字节序

 unsigned short ntohs(unsigned short); //short型数据从网络字节序转化为主机字节序

 unsigned short htonl(unsigned long);

 unsigned short ntonl(unsigned long);


 7.5 网络地址的初始化与分配

 将字符串信息转换为网络字节序的整数型

 sockaddr_in中保存地址信息的成员为32位整数型,为了分配IP,需要将其表示为32整数型数据

 .in_addr_t inet_addr(const char *string); // <arpa/inet.h>

 成功时返回32位大端序整型值,失败时返回INADDR_NONE,inet_addr函数不仅可以把IP地址转成32位整数型

 而且可以检测无效的IP地址,另外,从输出结果可以验证确实转换为网络字节序

 inet_aton函数与inet_addr函数在功能上完全相同,也将字符串形式IP地址转换为32位网络字节序整数并返

 回,只不过函数利用了in_addr结构体,且其使用频率更高

. int inet_aton(const char *string, struct in_addr *addr); 成功时返回1,失败时返回0

 string:含有需转换的IP地址信息的字符串地址值

 addr:将保存转换结果的in_addr结构体变量的地址值

 实际编程中若要调用inet_addr(),需要将转换后的IP地址信息代入sockaddr_in结构体中声明的in_addr

 结构体变量。而inet_aton()则不需此过程

. char *inet_ntoa(struct in_addr adr);把网络字节序整数型IP地址转换成孰悉的字符串形式,

 成功时返回转换的字符串地址值,失败时返回-1

 调用时需小心,返回值类型为char指针,返回字符串地址意味着字符串已保存到内存空间,但该函数未向程序员

 要求配内存,而是在内部申请了内存并保存了字符串,若需要长期保存,则应将字符串复制到其他内存空间。j

 7.7

 请求方法不同意味着调用的函数也不同,服务器端的准备工作通过bind()完成,而客户端则通过connect()完成

 因此,函数调用前需准备的地址值类型也不同。服务器端声明sockaddr_in结构体变量,将其初始化为赋予服务器

 IP和套接字的端口号,然后调用bind();而客户端则声明sockaddr_in结构体,并初始化为要与之连接的服务器

 端套接字的IP和端口叼,然扣调用connect();

 7.8 INADDR_ANY

 利用常数INADDR_ANY分配服务器端的IP地址。采用这种方式,则可自动获取运行服务器端的计算机IP地址,不必

 亲自输入。而且,若同一计算机中已分配多个IP地址(多宿主(Multi-homed)计算机,一般路由器属于这一类)

 则只要端口号一致,就可以从不同IP地址接收数据。因此服务端中优先考虑这种方式。而客户端中除非带有一部分

 服务器端功能,否则不会采用

 同一计算机中可以分配多个IP,实际IP的个数与计算机安装的NIC的数量相等。即使是服务器端套接字,也需要决定应

 接收哪个IP传来的(哪个NIC传来的)数据。因此,服务器端套接字初始化过程中要求IP地址信息,另外,若只有1

 NIC,则直接使用INADDR_ANY.

 7.9 向套接字分配网络地址

 上面用sockadr_in结构体初始化方法,接下来就把初始化的地址信息分配给套接字。bind()负责这项操作

 int bind(int sockfd,struct sockaddr *myaddr, socklen_t addrlen)

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

 sockfd:要分配地址信息(IP和端口号)的套接字文件描述符

 myaddr:存有地址信息的结构体变量地址值

 addrlen:第二个结构体变量的长度

 // exmaple:

 int endian_conv() {

 unsigned short host_port = 0x1234;

 unsigned short net_port;

 unsigned long host_addr = 0x12345678;

 unsigned long net_addr;

 net_port = htons(host_port);

 net_addr = htonl(host_addr);

 printf("host ordered port:%#x \n",host_port);

 printf("network ordered port:%#x \n",net_port);

 printf("host ordered address:%#lx \n",host_addr);

 printf("network ordered address:%#lx \n",net_addr);

 // inet_addr.c

 char *addr1 = "1.2.3.4";

 char *addr2 = "1.2.3.256";

 unsigned long cov_addr = inet_addr(addr1);

 if (cov_addr == INADDR_NONE) {

 printf("ERROR occured! \n");

 }else {

 printf("network ordered integer addr:%#lx \n",cov_addr);

 }

 cov_addr = inet_addr(addr2);

 if (cov_addr == INADDR_NONE) {

 printf("ERROR occured! \n");

 }else {

 printf("network ordered integer addr:%#lx \n",cov_addr);

 }

 // inet_aton.c

 char *addr = "127.232.124.79";

 struct sockaddr_in addr_inet;

 if (!inet_aton(addr,&addr_inet.sin_addr)) {

 error_handling("Converson error");

 }else {

 printf("network ordered integer addr:%#x \n",addr_inet.sin_addr.s_addr);

 }

 // inet_ntoa.c

 struct sockaddr_in addro,addrt;

 char *str_prt;

 char str_arr[20];

 addro.sin_addr.s_addr = htonl(0x1020304);

 addrt.sin_addr.s_addr = htonl(0x1010101);

 str_prt = inet_ntoa(addro.sin_addr);

 strcpy(str_arr,str_prt);

 printf("Dotted-Decimal notation1: %s \n",str_prt);

 inet_ntoa(addrt.sin_addr);

 printf("Dotted-Decimal notation2: %s \n",str_prt);

 printf("Dotted-Decimal notation3: %s \n",str_arr);

 // 网络地址的初始化

 struct sockaddr_in addrinit;

 char *serv_ip = "211,127.168.13";

 char *serv_port = "9190";

 memset(&addrinit,0,sizeof(addrinit));

 addrinit.sin_family = AF_INET;

 addrinit.sin_addr.s_addr = inet_addr(serv_ip);

// addrinit.sin_addr.s_addr = inet_addr(INADDR_ANY);

 addrinit.sin_port = htons(atoi(serv_port));

 return 0;

 }

 ps:后续再加...


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值