嵌入式笔记Linux —— 《Socket编程》

Socket编程编程(TCP)

Socket是BSD提供的网络应用编程接口,现在它已经是网络编程中的标准。

Socket是一种特殊的进程间通信方式,不同机器上的进程都可以使用这种方式进行通信

  1. 网络中的数据传输是一种I/O操作
  2. read、write、close操作可应用于Socket描述符
  3. Socket是一种文件描述符,代表了一个通信管道的一个端点
  4. 在Socket类型的文件描述符上,可以完成建立连接,数据传输等操作

常用的Socket类型有两种:
5. 流式Socket:SOCK_STREAM,提供面向连接的Socket
6. 数据报式Socket:SOCK_DGRAM,提供面向无连接的Socket

字节序

概念:是指多字节数据的存储顺序

分类:

  1. 小端格式:将低位字节数据存储在低地址
  2. 大端格式:将高位字节数据存储在低地址


特点:

  1. 网络协议指定了通讯字节序 —— 大端
  2. 只有在多字节数据处理时才需要考虑字节序
  3. 运行在同一台计算机上的进程相互通信时,一般不用考虑字节序
  4. 异构计算机之间通讯,需要转换自己的字节序为网络字节序
判断大小端示例代码:
  1 #include <stdio.h>                                   
  2 
  3 union 
  4 {
  5     short s;
  6     char c[sizeof(short)];
  7 }un;
  8 
  9 int main(int argc ,char *argv[])
 10 {
 11     un.s = 0x0102;
 12 
 13     if ( (un.c[0] == 1) && (un.c[1] == 2 ) )
 14     {
 15         printf("Big endian\n");
 16     }
 17     else if( (un.c[0] == 2) && (un.c[1] == 1) ) 
 18         {
 19             printf("Little endian\n");
 20         }
 21         
 22     return 0;
 23 }

运行结果:

字节序转换函数

主机字节序数据转换成网络字节序数据
uint32_t htonl(uint32_t hostint32);
uint16_t htons(uint16_t hostint16);
以上返回网络字节序数据

网络字节序数据转换成主机字节序数据
uint32_t ntohl(uint32_t netint32);
uint16_t ntohs(uint16_t netint16);
以上返回主机字节序数据

示例代码:
  1 #include <stdio.h>
  2 #include <arpa/inet.h>
  3 
  4 int main(int argc ,char *argv[])
  5 {
  6     int a = 0x01020304;                                                          
  7     short int b = 0x0102;
  8 
  9     printf("htonl(%08x) = %08x\n",a,htonl(a));
 10     printf("htons(%04x) = %04x\n",b,htons(b));
 11         
 12     return 0;
 13 }

运行结果:

sockaddr_in套接字地址结构:(重点)

头文件:#include <netinet/in.h>

在IPv4因特网域(AF_INET)中,套接字地址结构用sockaddr_in命名
struct in_addr
{
in_addr_t s_addr;//4字节
};

struct sockaddr_in
{
sa_family_t sin_family; //2字节
in_port_tsin_port; //2字节
struct in_addr sin_addr; //4字节
unsigned char sin_zero[8]; //8字节
};

sockaddr通用套接字地址结构

地址标识了特定通信域中的套接字端点,地址格式与特定通信域相关,为了使不同格式地址能被传入套接字函数,地址被强制转换成通用套接字地址结构

头文件:#include <netinet/in.h>

struct sockaddr
{
sa_family_t sa_family; //2字节
char sa_data[14]; //14字节
};

inet_pton()函数

头文件:#include <arpa/inet.h>

int inet_pton(int family, const char*strptr, void *addrptr);

功能:
将点分十进制数串转换成32位无符号整数

参数:

  1. family : 协议族
  2. strptr : 点分十进制数串
  3. addrptr : 32位无符号整数的地址

返回值:成功:1失败:其它

inet_ntop()函数

头文件:#include <arpa/inet.h>

const char *inet_ntop(int family, constvoid *addrptr, char *strptr, size_t len);

功能:
将32位无符号整数转换成点分十进制数串

参数:

  1. family :协议族
  2. addrptr : 32位无符号整数
  3. strptr :点分十进制数串
  4. lenstrptr:缓存区长度

len的宏定义
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46 //for ipv6

返回值:成功:则返回字符串的首地址 失败:返回NULL

创建套接字

创建套接字是进行任何网络通信时必须做的第一步

头文件:#include <sys/socket.h>
int socket(int family, int type,int protocol);

功能:创建一个用于网络通信的I/O描述符(套接字)

参数:

  1. family:协议族
    AF_INET,AF_INET6,AF_LOCAL,AF_ROUTE,AF_KEY
  2. type:套接字类型
    SOCK_STREAM,SOCK_DGRAM,SOCK_RAW,SOCK_SEQPACKET
  3. protocol:协议类别
    0,IPPROTO_TCP,IPPROTO_UDP,IPPROTO_SCTP

返回值:套接字

socket创建的套接字特点
使用socket创建套接字时,系统不会分配端口
使用socket创建的是主动套接字,但作为服务器,需要被动等待别人的连接

服务器

做为服务器需要具备的条件:

  1. 具备一个可以确知的地址,以便让别人找到我
  2. 让操作系统知道你是一个服务器,而不是一个客户端
  3. 等待连接的到来,对于面向连接的TCP协议来说,连接的建立才真正意味着数据通信的开始

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

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

功能:将本地协议地址与sockfd绑定

参数:

  1. sockfd: socket套接字
  2. myaddr: 指向特定于协议的地址结构指针
  3. addrlen:该地址结构的长度

返回值:成功:返回 失败:其他

bind示例代码:


注意:INADDR_ANY 通配地址,值为0

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

int listen(int sockfd, int backlog);

功能:

  1. 将套接字由主动修改为被动
  2. 使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接

参数:

  1. sockfd: socket监听套接字
  2. backlog:连接队列的长度

返回值:成功:返回 失败:其他

listen示例代码:


int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
功能:从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待

参数:

  1. sockfd: socket监听套接字
  2. cliaddr: 用于存放客户端套接字地址结构
  3. addrlen:套接字地址结构体长度

返回值:已连接套接字
注意:accept函数返回的是一个已连接套接字,这个套接字代表当前这个连接

accept示例:

在这里插入图片描述

客户端

作为客户端需要具备的条件:

  1. 知道服务器的IP地址以及端口号

int connect(int sockfd,const structsockaddr *addr,socklen_t len);

功能:
主动跟服务器建立链接
连接建立成功后才可以开始传输数据(对于TCP协议)

参数:

  1. sockfd:socket套接字
  2. addr: 需连接的服务器地址结构
  3. addrlen:地址结构体长度

返回值:成功:0b失败:其他
注意:connect函数建立连接之后不会产生新的套接字

connect示例:


在这里插入图片描述

数据传输

当连接建立后,通信的两端便具备两个套接字

套接字也是一种文件描述符,所以read、write函数可以用于从这个连接中取出或向其写入数据。

ssize_t send(int sockfd, const void* buf, size_t nbytes, int flags);

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

功能:用于发送数据

参数:

  1. sockfd: socket套接字
  2. buf: 待发送数据缓存区的地址znbytes: 发送缓存区大小(以字节为单位)
  3. flags: 套接字标志(常为0)

返回值:成功发送的字节数

注意:不能用TCP协议发送0长度的数据包

ssize_t recv(int sockfd, void *buf,size_tnbytes, int flags);

功能:用于接收网络数据

参数:

  1. sockfd: 套接字
  2. buf: 指向接收网络数据的缓冲区
  3. nbytes: 接收缓冲区的大小(以字节为单位)
  4. flags: 套接字标志(常为0)

返回值:成功接收到字节数

使用close函数即可关闭套接字

:关闭一个代表已连接套接字将导致另一端接收到一个0长度的数据包

做服务器时:

  1. 关闭socket创建的监听套接字将导致服务器无法继续接受新的连接,但不会影响已经建立的连接
  2. 关闭accept返回的已连接套接字将导致它所代表的连接被关闭,但不会影响服务器的监听

做客户端时:
:关闭连接就是关闭连接,不意味着其他

关于TCP建立和断开连接的具体过程请看我写的另一篇文章(TCP/IP)——(脑图+注释版 思路清晰)

Server端示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc,char *argv[])
{
	char recv_buf[2048] = "";//接收缓冲区
	int sockfd = 0;//定义套接字
	int connfd = 0;
	int err_log = 0;//错误标志
	struct sockaddr_in my_addr;//服务器地址结构体
	unsigned short port = 8000;//监听端口号

	if(argc > 1)//传参,
	{
		port = atoi(argv[1]);//指定port口
	}

	printf("TCP Server Started at port %d!\n",port);

	sockfd = socket(AF_INET,SOCK_STREAM,0);//创建TCP套接字  IPv4,流式套接字
	if(sockfd < 0)
	{
		perror("socket");
		exit(-1);
	}

	bzero(&my_addr,sizeof(my_addr));//清空服务器地址

	my_addr.sin_family = AF_INET;//IPv4
	my_addr.sin_port = htons(port);//字节序转换端口号
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);//字节序转换本机的IP地址

	printf("Binding server to port %d\n",port);

	err_log = bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));//讲服务器地址与套接字进行绑定

	if(err_log != 0)
	{
		perror("binding");
		close(sockfd);
		exit(-1);
	}

	err_log = listen(sockfd,10);//监听客户端的连接 10个连接数
	if(err_log != 0)
	{
		perror("listen");
		close(sockfd);
		exit(-1);
	}

	printf("Waiting client...\n");

	while(1)//等待客户端的连接
	{
		size_t recv_len = 0;
		struct sockaddr_in client_addr;//存放客户端的地址
		char cli_ip[INET_ADDRSTRLEN] = "";//存放客户端IP地址 点分十进制

		socklen_t cliaddr_len = sizeof(client_addr);//必须初始化

		connfd = accept(sockfd,(struct sockaddr*)&client_addr,&cliaddr_len);//接收连接
		if(connfd < 0)
		{
			perror("accept");
			continue;
		}

		inet_ntop(AF_INET,&client_addr.sin_addr,cli_ip,INET_ADDRSTRLEN);//点分十进制的转换

		printf("client ip = %s\n",cli_ip);

		while( (recv_len = recv(connfd,recv_buf,sizeof(recv_buf),0)) > 0)//把接收到的数据发送回去
		{
			send(connfd,recv_buf,recv_len,0);
		}

		close(connfd);//关闭已连接的套接字
		printf("client closed!\n");

	}

	close(sockfd);//关闭监听的套接字
	return 0;
}

Client端示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc,char *argv[])
{
    unsigned short port = 8000;//服务器的端口号
    char *server_ip = "192.168.222.183";//服务器的IP地址
    char send_buf[512] = "Hello every!";//发送的数据
    char recv_buf[512] = "";//接收缓冲区

    int sockfd = 0;//定义套接字
    int err_log = 0;//定义错误标志

    struct sockaddr_in server_addr;//非通用套接字地址结构体

    if( argc > 1)//利用传参,更改服务器的IP地址
    {
        server_ip = argv[1];
    }

    if( argc > 2)//利用传参,更改服务器的端口号
    {
        port = atoi(argv[2]);
    }

    bzero(&server_addr,sizeof(server_addr));//初始化服务器地址
    server_addr.sin_family = AF_INET;//IPv4
    server_addr.sin_port = htons(port);//端口号
    inet_pton(AF_INET,server_ip,&server_addr.sin_addr);

    sockfd = socket(AF_INET,SOCK_STREAM,0);//创建通信端点:套接字
    if(sockfd < 0)
    {
        perror("socket");
        exit(-1);
    }
    /* 连接服务器 */ 
    err_log = connect(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(err_log != 0)
    {
        perror("connect");
        close(sockfd);
        exit(-1);
    }

    send(sockfd,send_buf,strlen(send_buf),0);//向服务器发送数据
    recv(sockfd,recv_buf,strlen(recv_buf),0);//接收服务器的发回的信息
    printf("%s\n",recv_buf);

    close(sockfd);//关闭套接字

    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数字梦想家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值