socket网络编程基础篇-------如何写一个简单的TCP服务器

 

目录

socket通信的预备知识  

1.什么是socket通信?

2.什么是字节序?

3.什么是IP地址转换?

4.什么是网络地址格式?

5.什么是“半关闭”?

6.为什么会用到"半关闭"? 

socket通信的具体过程

1.概述

2.socket函数

3.bind函数

4.listen函数,connect函数,accept函数

5.send函数,sendto函数,sendmsg函数

6.recv函数,recvfrom函数,recvmsg函数

7.close函数 shutdown函数

socket通信的实例(TCP和UDP服务器)

tcp服务器

tcp客户端

udp服务器

udp客户端

参考



刚刚过去的研一上学期,
一直在恶补cs的基础知识,同时也在实验室做了一些小项目,
由于平常习惯把学的东西整理思维导图xmind,趁寒假有时间,
打算接下来把这几个月学习的一些东西从思维导图整理到博客,算是一次梳理和复习。
(包括操作系统,计算机网络,git,linux命令,数据结构,医学图像处理库ITK vtk,cmake,QT,javascript的以及一些项目)
由于最近在学习apue的后面socket通信部分,所以就从这里开始吧 。
 

 

socket通信的预备知识  

1.什么是socket通信?

我们知道操作系统中一个主机中进程通信方式有比如管道 信号 共享内存等等,
那通过网络相连的不同主机间的进程该如何通信呢?(比如我电脑上的一个QQ要给你电脑上的一个QQ发消息)
其中的一个机制是socket套接字(其实socket也可以支持同一主机下的进程间通信)
在计算机网络中我们知道,支持网络间通信的主流协议是TCP/IP协议
所以socket通信也得依据这个协议,在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,
IP地址指定了网络中是哪一台计算机 端口号指定了该计算中的哪一个进程
所以socket绑定一个“IP地址+端口号”就可以实现网络中两个特定主机间的特定进程通信

2.什么是字节序?

  • 字节序有哪些类型?

所谓的字节序我理解就是 数据在计算机内存中的不同存储方式 它有两种:小端序和大端序

计算机内部进行运算的时候都是沿着内存地址增大的方向逐个读取字节然后运算的,
而一般运算的时候都是先进行低位运算再进行高位运算效率会比较高,
比如我们要乘法运算时都会从低位开始相乘往在一个个往高位算 计算机内部也是这个道理

所以就得保证在计算机沿着内存地址增大的方向逐个读取数据时,是先读取低位字节再读取高位字节

比如有一个十六进制的数据0x1234 其中0x12是高字节 0x34是低字节,为了保证计算的高效

它在内存中存储的时候得是在内存地址小的地方是低字节0x34 在内存大的地方是高字节0x12 

这种数据在内存中的存储方式就是小端序  

下面介绍与小端序相反的大端序

比如有一个十六进制的数据0x1234 其中0x12是高字节 0x34是低字节
它在内存中存储的时候得是在内存地址小的地方是高字节0x12 在内存大的地方是低字节0x34

这就是大端序
计算机的内部处理都是小端字节序。但是,人类还是习惯读写大端字节序。
除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。

  • 为什么需要字节序之间的转换?
    目前常见的计算机内部处理器架构都是小端字节序 而TCP/IP协议所规定的网络字节序是大端字节序
    所以在网络传输时就需要对字节序进行转换
  • 字节序转换如何实现?
    根据APUE一书所写,UNIX操作系统为字节序转换提供了四个函数
    #include <arpa/inet.h>
    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohs(uint16_t netshort);
    h表示host,n表示network,l表示32位长整数,s表示16位短整数。
    如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,
    如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
            #include <stdio.h>
     	#include <netinet/in.h>
     	int main()
     	{
    	    int i_num = 0x12345678;
    	    printf("[0]:0x%x\n", *((char *)&i_num + 0));
     	    printf("[1]:0x%x\n", *((char *)&i_num + 1));
     	    printf("[2]:0x%x\n", *((char *)&i_num + 2));
     	    printf("[3]:0x%x\n", *((char *)&i_num + 3));
     	 
     	    i_num = htonl(i_num);
     	    printf("[0]:0x%x\n", *((char *)&i_num + 0));
     	    printf("[1]:0x%x\n", *((char *)&i_num + 1));
     	    printf("[2]:0x%x\n", *((char *)&i_num + 2));
    	    printf("[3]:0x%x\n", *((char *)&i_num + 3));
     	 
     	    return 0;
    	} 
    
    在80X86CPU平台上,执行该程序得到如下结果: 
    [0]:0x78 
    [1]:0x56 
    [2]:0x34 
    [3]:0x12
    
    [0]:0x12 
    [1]:0x34 
    [2]:0x56 
    [3]:0x78

     

3.什么是IP地址转换?

通常我们认识的ip地址 是192.168.6.250这样的格式,这种格式叫点分十进制
而在网络编程中我们常常需要把这种ip地址转换成二进制格式去处理
也就是说我们需要一组函数实现点分十进制的ip地址转换成二进制的ip地址格式
于是在unix中引入了一组函数去实现这样的操作

#include<arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr);
                                    
返回:若成功则为1,若输入不是有效的表达格式则为0,若出错则为-1
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
                                               
返回:若成功则为指向结果的指针, 若出错则为NULL

首先说第一个函数 inet_pton:
这个函数功能是将点分十进制的字符串格式ip转换成网络传输中的二进制格式ip 
family参数只能是是宏定义AF_INET,或者AF_INET6。分别表示要转换的是ipv4或者ipv6,传入其他参数将返回一个错误,并将errno置为EAFNOSUPPORT
strptr表示指向被转换的字符串形式的点分十进制ip
addrptr表示存储转换后的二进制格式的ip
再说第二个函数inet_ntop
family,strptr参数,addrptr功能同上
说一说len参数,len参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。
可以用在<netinet/in.h>头文件中进行的宏定义

#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46

分别表示存放ipv4地址的空间大小和存放ipv6地址的的空间大小
也可以自行去定义len的大小,但是如果len太小,不足以容纳表达式结果(包括结尾的空字符),
那么返回一个空指针,并置errno为ENOSPC
同时inet_ntop函数的strptr参数不可以是一个空指针。
调用者必须为目标存储单元分配内存并指定其大小。调用成功时,这个指针就是该函数的返回值。

4.什么是网络地址格式?

  • sockaddr

前面说到,一个socket套接字必须绑定一个ip地址和端口号才能实现特定主机中的特定进程间的通信
而在unix中表示ip地址和端口号并不是两个变量分开来表示,而是整合到一个结构体
这个结构体就是sockaddr(如下)。

sockaddr结构体在#include <sys/socket.h>中定义,
sa_family表示一个地址族 

sa_family地址族 表示一般都是“AF_xxx”的形式,
它的值包括三种:AF_INET,AF_INET6和AF_UNSPE。
AF_INET(又称 PF_INET)是 IPv4 网络协议的套接字类型,
AF_INET6 则是 IPv6 的;
而 AF_UNIX 则是 Unix 系统本地通信。


sa_data[14]存放ip地址和端口号
总共的大小是16字节
在没有引入ipv6之前,是一直使用sockaddr这个结构体的,在一些要用到ip地址和端口号的函数(比如绑定的bind函数)中 
直接把这个结构体传进去就行了,但是引入了ipv6以后,如果ipv4和ipv6都用这个结构体表达就会有些混乱
但是如果重新引入新的结构体去分别表达ipv4和ipv6就会出现新的问题,
就是一些用到ip地址端口号的函数原来都是用sockaddr结构体作为参数传入的
引入了新的表达方式 为了用上这些新的表达方式下的结构体,可能这些函数(比如bind)的底层就得重新写,很麻烦
于是就想了一个办法引入还是得引入 但是就是向这些函数传递参数时用强制转换的方式把新引入的结构体转换成原来的结构体
以bind函数为例子 
bind函数的参数定义是这样的
注意它的第二个参数是一个指向sockaddr结构体的指针addr
现在真正使用时会用强制转换运算符把传进去的参数进行转换

比如下面的servaddr可能是新引入的表示ipv4或者ipv6的结构体

  • sockaddr_in

新引入的iPv4和IPv6的结构体=定义在头文件netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,
它的地址族用常数AF_INET表示 数据类型in_port_t定义成uint16_t ,in_addr_t定义成uint32_t



该结构体的赋值方法

  • 13
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值