TCP套接字编程

开启网络编程新篇章

hello,大家好,我是雨墨,小老弟我正在学习网络编程,想着既然都写了笔记,那么就分享出来吧,如果笔记中有什么问题,欢迎大家斧正!如果对你有用,那就点个赞再走吧~

理解网络编程和套接字

linux 头文件 #include <sys/socket.h>

基于linux平台的实现

服务器端

1. 调用socket函数创建套接字	// 建立电话线
int socket(int domain,int type ,int protocol);
int serv_sock(PF_INET, SOCK_STREAM, 0);

2. 调用bind函数分配IP地址和端口号	// 给你一个手机号
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
if (bind(serv_sock, (struct sockaddr)&serv_addr, sizoef serv_addr) == -1)
    ...
    
3. 调用listen函数转化为可接收请求状态	// 在电话机前等
int listen(int sockfd, int backlog);	// backlog 表示消息队列的长度,自己设定
if (listen(serv_sock, 5) == -1)
    ...
    
4. 调用accept函数受理连接请求	// 接电话
int accept(int sockfd, struct sockaddr *addr , socklen_t *addrlen);	// 创建 client sockfd
socklen_t clnt_addr_sz  = sizeof clnt_addr;
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_sz)

客户端

1、调用 socket 函数创建套接字
int socket(int domain,int type ,int protocol);
int sock = socket(PF_INET, SOCK_SRTREAM, 0);

2、调用 connect 函数       
int connect(int socketfd, struct sockadd* serv_addr, socklen_t addrlen);
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof serv_addr) == -1)
    ...

Linux 不区分文件和套接字,Linux 下万物皆是文件。

打开文件 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
open(const char *path , int flag);// path为文件地址, flag为文件开始模式,可能有多个,由|连接
例如 int fd = open("data.txt",O_CREAT|O_WRONLY|O_TRUNC);

O_CREAT     必要时创建文件
O_TRUNC     删除全部现有数据
O_APPEND    维持现有数据,保存到后面
O_RDONLY    只读打开
O_WRONLY    只写打开
O_RDWR      读写打开

关闭文件
#include <unistd.h>
int close(int fd);// fd为文件描述符

将数据写入文件
#include <unistd.h>
ssize_t write(int fd,const void * buf ,size_t nbytes)
size_t为无符号整形(unsigned int)的别名, ssize_tsigned int 类型
其中可以向数字写数据,例如 write(sock, (char*)&str_len, 4);	// 解释:str_len是int,向str_len中写4个Byte的int,然后转为char
通常是预先告知对方要发的字符串的大小为多少
    
读取文件中数据
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t nbytes);
// fd 文件描述符 ,buf 保存接收数据缓冲地址值 nbytes 接收数据最大字节数
也可以向数字读数据,例如 read(sock, (char*)&str_len, 4)	// 解释:读入四个字节存入str_Len中

套接字类型与协议设置

int socket(int domain, int type ,int protocol)

domain : 套接字中使用的协议族信息,本书采用 ipv4 ,所以使用 PF_INET

type: 套接字数据传输类型信息,按照面向连接传输分为 SOCK_STREAM ,按照面向数据从传输分为 SOCK_DGRAM

protocol: 计算机间通信使用的协议信息,由于面向连接传输且在 ipv4 中的是 IPPROTO_TCP ,所以根据前面两个信息可以推出它来,可以直接写 0 。同理 IPPROTO_UDP 也可直接用 0 表示。

协议族 : 协议分类信息,本书主要讨论 IPv4 ,所以使用的一般是 PF_INET

PF_INET         IPv4互联网协议族
PF_INET6        IPv6
PF_LOCOL        本地通信的UNIX协议族
PF_PACKET       底层套接字的协议族
PF_IPX          IPX Novell协议族

套接字类型(type):套接字的数据传输方式

  1. 面向连接的套接字(SOCK_STREAM)

    特征:可靠,按序基于字节的面向连接(一对一)的数据传输方式的套接字 ,想象为传送带上传送东西,socket 相当于站在两个车间的工人。

  2. 面向消息的的套接字(SOCK_DGRAM)

    特征: 不可靠,不按序,以数据的高速传输为目的的套接字,可以想象成骑着摩托车送快递的小哥,快递送的很快,但是容易坏,并且每次只能送那么多。

具体指定协议信息(protocol)

为啥需要第三个参数: 同一协议族中存在多个数据传输方式相同的协议

TCP 套接字(IPPROTO_TCP), write 函数调用次数可以和不同于 read 函数调用次数,由于在 ipv4 协议族中有且只有那个 SOCK_STREAM 的只有 IPPROTO_TCP,所以第三个参数可以直接写 0 。

地址族与数据序列

分配给套接字的IP地址与端口号

IP是为收发网络数据而分配给计算机的值,端口号是为区分程序中创建的套接字而分配给套接字的序号

IPv4: 4 字节地址族 IPv6 : 16 字节地址族

IPv4标准的 4 字节IP地址分为网络地址和主机地址,且根据网络ID和主机ID所占字节的不同,分为A(0-127),B(128-191),C(192-223),D,E

主机传输数据是先根据网络ID发送到相应路由器或交换机然后在根据主机ID向目标主机传递数据

端口号是在同一操作系统内区分不同套接字而设置的。不能将同一端口号分给不同套接字,但是 TCP 和 UDP 不会共用端口号,所以允许UDP和TCP使用同一端口号

地址信息的表示

表示 IPV4 地址的结构体

struct sockaddr_in
{
   
    sa_family_t sin_family;  // 地址族(Address Family)
    uint16_t sin_port;       // 16 位 TCP/UDP 端口号,以网络字节序保存
    struct in_addr sin_addr; // 32位 IP 地址
    char sin_zero[8];        // 不使用,必须填充为0,使sockaddr_in和sockadd结构体保持一致
};

该结构体中提到的另一个结构体 in_addr 定义如下,它用来存放 32 位IP地址

struct in_addr
{
   
    in_addr_t s_addr; //32位IPV4地址
}

数据类型名称 数据类型说明 声明的头文件
int 8_t signed 8-bit int sys/types.h
uint8_t unsigned 8-bit int (unsigned char) sys/types.h
int16_t signed 16-bit int sys/types.h
uint16_t unsigned 16-bit int (unsigned short) sys/types.h
int32_t signed 32-bit int sys/types.h
uint32_t unsigned 32-bit int (unsigned long) sys/types.h
sa_family_t 地址族(address family) sys/socket.h
socklen_t 长度(length of struct) sys/socket.h
in_addr_t IP地址,声明为 uint_32_t netinet/in.h
in_port_t 端口号,声明为 uint_16_t netinet/in.h
struct sockaddr
{
   
    sa_family_t sin_family; //地址族
    char sa_data[14];       //地址信息,包括IP地址和端口号,剩余部分填充为0
}

网络字节序和地址变换

CPU保存数据方式有两种:1. 大端序(高位字节存放到低位地址) 2. 小端序(高位字节存放到高位地址)

例如0x123456,大端序为 0x12345678 小端序为 0x78563412

为保证数据正常接收,电脑都是先把数组转换为大端序再进行网络传输。网络字节序是大端序

unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

htons 的 h 代表主机(host)字节序。transport short data from host to network
htons 的 n 代表网络(network)字节序。	transport short data from network to host
s 代表 short
l 代表 long

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
   
    unsigned short host_port = 0x1234;
    unsigned short net_port = htons(host_port); //转换为网络字节序;
    unsigned long host_addr = 0x12345678;
    unsigned long 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);

    return 0;
}

假设在小端序cpu上运行
Host ordered port: 0x1234
Network ordered port: 0x3412
Host ordered address: 0x12345678
Network ordered address: 0x78563412

网络地址的初始化与分配

sockaddr_in 保存地址信息的是 32 位整数,我们要将点分十进制表示的 IP 地址转换为 32 位整数可以通过

#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);
//成功时返回32位大端序整数,失败时返回InADDR_NONE

实例:

#include <stdio.h>
#include <arpa/inet.h>
int main(
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值