Linux嵌入式学习—网络编程

1.socket概念

Linux中的网络编程是通过socket接口来进行的。socket是一种特殊的I/O接口,它也是一种文件描述符。它是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信,socket也有一个类似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立、数据传输等操作都是通过socket来实现的;

2.socket类型

1)流式socket(SOCK_STREAM) à用于TCP通信
流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。
(2)数据报socket(SOCK_DGRAM) à用于UDP通信
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
(3)原始socket (SOCK_RAW) à用于新的网络协议实现的测试等
原始套接字允许对底层协议如IP或ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。 

3.socket信息数据结构

struct sockaddr
{
 unsigned short sa_family; /*地址族*/
 char sa_data[14]; /*14字节的协议地址,包含该socket的IP地址和端口号。*/
};
struct sockaddr_in
{
     short int sa_family; /*地址族*/
     unsigned short int sin_port; /*端口号*/
     struct in_addr sin_addr; /*IP地址*/
     unsigned char sin_zero[8]; /*填充0 以保持与struct sockaddr同样大小*/
};
struct in_addr
{
unsigned long int  s_addr; /* 32位IPv4地址,网络字节序 */
};
头文件<netinet/in.h>
sa_family:AF_INET  IPv4协议   AF_INET6  IPv6协议
国内一般使用的都是IPv4

4.数据存储优先顺序的转换

字节序:即字节在电脑中存放时的序列与输入(输出)时的序列是先到的在前还是后到的在前。
1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址
网络中使用大大端字节序(Big endian)
四个函数:htons(),ntohs(),htonl()和ntohl().这四个地址分别实现网络字节序和主机字节序的转化,这里的h代表host,n代表network,s代表short,l代表long。通常16位的IP端口号用s代表,而IP地址用l来代表


IPv4的函数原型:
#include <sys/socket.h>
	  #include <netinet/in.h>
	  #include <arpa/inet.h>
	int inet_aton(const char *straddr, struct in_addr *addrptr);
	char *inet_ntoa(struct in_addr inaddr);
	in_addr_t inet_addr(const char *straddr);
函数inet_aton():将点分十进制数的IP地址转换成为网络字节序的32位二进制数值。返回值:成功,则返回1,不成功返回0

参数straddr:存放输入的点分十进制数IP地址字符串。
参数addrptr:传出参数,保存网络字节序的32位二进制数值。
函数inet_ntoa():将网络字节序的32位二进制数值转换为点分十进制的IP地址。
函数inet_addr():功能与inet_aton相同,但是结果传递的方式不同。inet_addr()若成功则返回32位二进制的网络字节序地址。

5.socket函数

生成一个套接口描述符
原型:`int socket(int domain,int type,int protocol);`
Domain   AF_INET:Ipv4网络协议
Type   tcp:SOCK_STREAM
protocolà指定socket所使用的传输协议编号。通常为0.

返回值:成功则返回套接口描述符,失败返回-1

6.bind函数

用来绑定一个端口号和IP地址,使套接口与指定的端口号和IP地址相关联。
原型:`int bind(int sockfd,struct sockaddr * my_addr,int addrlen);`
参数:sockfd 为前面socket的返回值。
  my_addr为结构体指针变量
Addrlen   长度

7.listen函数

使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求。如果客户端有连接请求,端口就会接受这个连接。
原型:`int listen(int sockfd, int backlog);`
参数:sockfd 为前面socket的返回值.即s_fd
Backlog  指定同时能处理的最大连接要求,通常为10或者5。 最大值可设至128
返回值:成功则返回0,失败返回-1

8.accept函数

接受远程计算机的连接请求,建立起与客户机之间的通信连接。服务器处于监听状态时,如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求。当accept函数接受一个连接时,会返回一个新的socket标识符,以后的数据传输和读取就要通过这个新的socket编号来处理,原来参数中的socket也可以继续使用,继续监听其它客户机的连接请求

原型:`int accept(int s,struct sockaddr * addr,int * addrlen);`
参数:s 为前面socket的返回值.即s_fd
原型:int accept(int s,struct sockaddr * addr,int * addrlen);
参数:s 为前面socket的返回值.即s_fd
addr为结构体指针变量,和bind的结构体是同种类型的,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中。
addrlen 表示结构体的长度,为整型指针
返回值:成功则返回新的socket处理代码new_fd,失败返回-1

9.send函数

用新的套接字发送数据给指定的远端主机
原型:int send(int s,const void * msg,int len,unsigned int flags);
参数:s 为前面accept的返回值
返回值:成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回-1
11.connect函数( 客户端 ):
用来请求连接远程服务器,将参数sockfd 的socket 连至参数serv_addr 指定的服务器IP和端口号上去。
原型:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
参数:sockfd 为前面socket的返回值,即s_fd
  serv_addr 为结构体指针变量,存储着远程服务器的IP与端口号信息。
  addrlen 表示结构体变量的长度
返回值:成功则返回0,失败返回-1


close函数:当使用完文件后若已不再需要则可使用close()关闭该文件,并且close()会让数据写回磁盘,并释放该文件所占用的资源
原型:int close(int fd);
参数:fd 为前面的s_fd,new_fd
  返回值:若文件顺利关闭则返回0,发生错误时返回-1

10.例子

服务端:

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


int main(int argc,char **argv)
{
	int s_fd;
	int nread;
	char readBuf[128];

	char msg[128] = {0};
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;

	if(argc != 3){
		printf("param not ok!\n");
		exit(0);
	}

	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	//socket
	//AF_INET    IPv4 Internet protocols   ip(7)
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1){
		perror("socket");
		exit(-1);
	}

	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&s_addr.sin_addr);

	//bind
	bind(s_fd,(struct	sockaddr	*)&s_addr,sizeof(struct sockaddr_in));
	
	//listen
	listen(s_fd,10);

	//accept
	int clen = sizeof(struct sockaddr_in);

	while(1){
		int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1){
			perror("accept");
		}
		
		printf("get connect%s\n",inet_ntoa(c_addr.sin_addr));
		//等待客户端连接
		if(fork() == 0){
			//若有客户端连接,fork一个子进程去处理,父进程继续等待
			if(fork() == 0){
				while(1){
					//send
					memset(msg,0,sizeof(msg));
					printf("input:\n");
					gets(msg);
					write(c_fd,msg,strlen(msg));
				}
			}

			while(1){
				//read
				memset(readBuf,0,sizeof(readBuf));
				nread = read(c_fd,readBuf,128);
				if(nread == -1){
					perror("read");
				}
				else{
				printf("get message %d bytes :%s\n",nread,readBuf);
				}
			}
			break;	
		}
	}
	return 0;
}

客户端:

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


int main(int argc,char **argv)
{
	int c_fd;
	int nread;
	char readBuf[128];

	char msg[128] = {0};
	struct sockaddr_in c_addr;

	 if(argc != 3){
                printf("param not ok!\n");
                exit(-1);
        }

	memset(&c_addr,0,sizeof(struct sockaddr_in));

	//socket
	//AF_INET    IPv4 Internet protocols   ip(7)
	c_fd = socket(AF_INET,SOCK_STREAM,0);
	if(c_fd == -1){
		perror("socket");
		exit(-1);
	}

	c_addr.sin_family = AF_INET;
	c_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&c_addr.sin_addr);

	//connect
	int len = sizeof(struct sockaddr);
	if(connect(c_fd,(struct sockaddr *)&c_addr,len) == -1){
		perror("connect");
		exit(-1);
	}
	
	while(1){	
		if(fork() == 0){
			while(1){
			       //write
			       memset(msg,0,sizeof(msg));
			       printf("input,please.\n");
			       gets(msg);
			       write(c_fd,msg,strlen(msg));
			}

		}
		while(1){
			//read
			memset(readBuf,0,sizeof(readBuf));
			nread = read(c_fd,readBuf,128);
			if(nread == -1){
				perror("read");
			}
			else{
				printf("get message from server %d bytes :%s\n",nread,readBuf);
			}
		}
	}
	return 0;	


}

改进服务端:

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


int main(int argc,char **argv)
{
	int s_fd;
	int nread;
	char readBuf[128];
	int mark = 0;
	char msg[128] = {0};
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;

	if(argc != 3){
		printf("param not ok!\n");
		exit(0);
	}

	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	//socket
	//AF_INET    IPv4 Internet protocols   ip(7)
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1){
		perror("socket");
		exit(-1);
	}

	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&s_addr.sin_addr);

	//bind
	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
	
	//listen
	listen(s_fd,10);

	//accept
	int clen = sizeof(struct sockaddr_in);

	while(1){
		int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1){
			perror("accept");
		}
		
		mark++;//记录每一个客户端
		printf("get connect%s\n",inet_ntoa(c_addr.sin_addr));
		
		if(fork() == 0){
			if(fork() == 0){
				while(1){
					//send
					sprintf(msg,"welcom NO.%dclient\n",mark);
					write(c_fd,msg,strlen(msg));
					sleep(2);//每两秒向客户端发送数据,保证连接
				}
			}

			while(1){
				//read
				memset(readBuf,0,sizeof(readBuf));
				nread = read(c_fd,readBuf,128);
				if(nread == -1){
					perror("read");
				}
				else{
				printf("get message %d bytes :%s\n",nread,readBuf);
				}
			}
			break;	
		}
	}
	return 0;
}

因为多个客户端同时接入时会有资源竞争,所以在服务端在向发送数据时,不确定那个客户端能收到,并且只有一个客户端能收到,所以对服务端进行简易改进,实现多方数据通信。首先不断向客户端发送数据,确保通讯,然后不同客户端可任意向客服端发送数据,服务端可监听所有数据。

此次改进只为让服务端成为一个中转站,小改一下,太复杂的目前水平无法实现。。。

能力一般,水平有限,学识浅薄,希望能帮到您

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值