Linux系统编程——socket套接字网络编程

TCP / UDP 对比

1、TCP 面向连接(如打电话要先拨号建立连接);UDP 是无连接的,即发送数据之前不需要建立连接
2、TCP 提供可靠的服务,也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付
3、TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流;UDP 是面向报文的,UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 IP 电话,实时视频会议等)
4、每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和多对多的交互通信
5、TCP 首部开销20字节;UDP 的首部开销小,只有8个字节
6、TCP 的逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道

字节序

字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序

  • Little endian:将低序字节存储在起始地址
  • Big endian:将高序字节存储在起始地址

x86系列 CPU 都是 little-endian 的字节序
网络字节序:big endian

字节序转换 api:

#include<arpa/inet.h>
uint16_t htons ( uint16_t hostshort );  //返回网络字节序的值
uint32_t htonl ( uint32_t hostlong );   //返回网络字节序的值
uint16_t ntohs ( uint16_t netshort );   //返回主机字节序的值
uint32_t ntohl ( uint32_t netlong );    //返回主机字节序的值
// h 代表 host ,n 代表 net,s 代表 short(2个字节),l 代表 long(4个字节)

socket 套接字网络编程

步骤:

在这里插入图片描述

函数原型:
  • socket()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);  // 创建 socket 套接字

// 返回值:成功返回套接字文件描述符,失败返回 -1
 
// domain 参数:通信协议
       AF_UNIX, AF_LOCAL   // 本地通信
       AF_INET             // IPv4 互联网协议
       AF_INET6            // IPv6 互联网协议
       AF_IPX              // IPX - Novell 协议
       AF_NETLINK          // 内核用户界面设备
       AF_X25              // ITU-T X.25 / ISO-8208 协议
       AF_AX25             // Amateur radio AX.25 协议
       AF_ATMPVC           // 访问原始 ATM PVC
       AF_APPLETALK        // Appletalk 公司,苹果计算机公司通讯协议系列
       AF_PACKET           // 低级分组接口

// type 参数:套接字类型描述
       SOCK_STREAM     // TCP
       SOCK_DGRAM      // UDP
       SOCK_RAW		   /* 允许程序使用底层协议,原始套接字允许对底层协议和 IP 或 ICMP 进行直接访问,功能强大但使用较为不方
                       便,主要用于一些协议的开发 */
//     more......

// protocol 参数:套接口协议类型;通常赋 0 值,指定选择与 type 参数 类型对应的协议
       0				// 指定要与套接字一起使用的特定协议
       IPPROTO_TCP
       IPPROTO_UDP
       IPPROTO_SCTP
       IPPROTO_TIPC
//     more......
  • bind()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);  // 将 socket 套接字与端口绑定

// 返回值:成功返回 0,失败返回 -1

// sockfd 参数:套接字文件描述符

/* addr 参数:是一个指向包含本机 IP 地址及端口号等信息的 sockaddr 类型的指针,指向要绑定给 sockfd 的协议地址结构,这个地址
            结构根据地址创建 socket 时的地址协议族的不同而不同 */
            
            struct sockaddr {
    
    
            	sa_family_t sa_family ;  //协议族
            	char        sa_data[14] ;  // IP+端口
            };
            
//		一般用 sockaddr_in 结构体同等替换 sockaddr 结构体:
            
            #include <netinet/in.h>  // 或 #include <linux/in.h>
            struct sockaddr_in {
    
    
    			sa_family_t    sin_family;  //协议族(常用AF_INET)
    			in_port_t      sin_port;  //端口号,一般用 5000~9000
    			struct in_addr sin_addr;  // IP地址结构体
    			unsigned char  sin_zero[8];  /*填充   没有实际意义,只是为跟 sockaddr 结构在内存中对齐,这样两者才能相互
    												  转换*/
			};

//		IP地址结构如下:为32位字

			struct in_addr {
    
    
    			uint32_t       s_addr;     // 按网络字节顺序排列的地址
			};
			
//		地址转换函数:
			
			#include <sys/socket.h>
       		#include <netinet/in.h>
       		#include <arpa/inet.h>
       		int inet_aton(const char *cp, struct in_addr *inp);  /* 将 Internet 主机地址 cp 从 IPv4 数字和点表示法转换
       		为二进制形式(按网络字节顺序),存放在 inp 指向的结构中 */
       		in_addr_t inet_addr(const char *cp);  /* 将 Internet 主机地址 cp 从 IPv4 数字和点表示法转换为按网络字节顺序
       		排列的二进制数据 */

       		char *inet_ntoa(struct in_addr in);  /* 将以网络字节顺序给定的 Internet 主机地址转换为IPv4 点分十进制表示法
       		中的字符串 */
       		
       		in_addr_t inet_network(const char *cp);
       		struct in_addr inet_makeaddr(int net, int host);
       		in_addr_t inet_lnaof(struct in_addr in);
       		in_addr_t inet_netof(struct in_addr in);


// addrlen 参数:addr 参数指向的结构体的大小,用 sizeof() 计算

寻找 sturct sockaddr_in 所处头文件:
在 /usr/include/ 路径下输入:

grep "struct sockaddr_in {" * -nir

解析:

grep "......" * -nir  // 命令
// *:当前目录
// n:显示行号
// i:不区分大小写
// r:递归
  • listen()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);  // 设置监听,监听被绑定端口

// 返回值:成功返回 0,失败返回 -1
// sockfd 参数:socket 套接字文件描述符,socket 函数的返回值
// backlog 参数:指定在请求队列中允许的最大请求数
  • accept()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);  // 接收连接请求,阻塞至有客户端完成三次握手

// 返回值:该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符
/* 一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接
套接字(表示 TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭 */

// sockfd 参数:socket 套接字文件描述符,socket 函数的返回值

// addr 参数:用来返回已连接的对端(客户端)的协议地址,不关心时可用 NULL,sockaddr 结构体见 bind() 函数

// addrlen 参数:客户端地址长度,addr 参数指向的结构体的长度,用 sizeof(),不关心时可用 NULL
  • connect()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 返回值:成功返回 0,失败返回 -1
// sockfd 参数:是目的服务器端的 sockect 文件描述符
// addr:需要连接服务器端的 IP 地址和端口号的地址,sockaddr 结构体见 bind() 函数
  • 数据收发常用 api:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • close()
#include <unistd.h>
int close(int fd);  // 关闭 socket 套接字

示例

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

void main(int argc,char **argv)
{
    
    
        int s_fd;
        int c_fd;
        int n_read;
        char *readBuf = (char *)malloc(128);
        memset(readBuf,0,128);
        char *msg = "send successful!";

        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

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

		if(argc != 3){
    
    
				printf("Parameter error");
				exit(-1);
		}

        //int socket(int domain, int type, int protocol);
        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);

        //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        if(bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in)) == -1){
    
    
        		perror("bind");
        		exit(-1);
        }

        //int listen(int sockfd, int backlog);
        if(listen(s_fd,10) == -1){
    
    
        		perror("listen");
        		exit(-1);
        }

        socklen_t clen = sizeof(struct sockaddr_in);
        
        while(1){
    
    
                //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
                c_fd = accept(s_fd,(struct sockaddr*)&c_addr,&clen);
                if(c_fd == -1){
    
    
                        perror("accept");
                        exit(-1);
                }
                printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));

                if(fork() == 0){
    
    
                        n_read = read(c_fd,readBuf,128);
                        if(n_read == -1){
    
    
                                perror("read");
                                exit(-1);
                        }else{
    
    
                                printf("get message:%d,%s\n",n_read,readBuf);
                        }

                        write(c_fd,msg,strlen(msg));
                        break;
                }
        }
}
Client-side:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
//#include<linux/in.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>

void main(int argc, char **argv)
{
    
    
        int c_fd;
        int n_read;
        char readBuf[128];
        char *msg = "msg from client";
        struct sockaddr_in c_addr;
        memset(&c_addr,0,sizeof(struct sockaddr_in));

		if(argc != 3){
    
    
				printf("Parameter error");
				exit(-1);
		}
		
		// int socket(int domain, int type, int protocol);
        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);
        
		// int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
    
    
                perror("connect");
                exit(-1);
        }

        write(c_fd,msg,strlen(msg));

        n_read = read(c_fd,readBuf,128);
        if(n_read == -1){
    
    
                perror("read");
                exit(-1);
        }else{
    
    
                printf("get message form serve:%d,%s\n",n_read,readBuf);
        }
}
运行结果:

Server:

:~$ ./sockserve 127.0.0.1 8888
get connect:127.0.0.1
get message:15,msg from client

Client-side:

扫描二维码关注公众号,回复: 12487956 查看本文章
:~$ ./sockclient 127.0.0.1 8888
get message form serve:16,send successful!

FTP 简易版

Server:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/socket.h>
//#include<linux/in.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>

#define LS     1		// 列出云端所有文件夹
#define PWD    2		// 云端的当前路径
#define GET    3		// 获取云端文件
#define LCD    4		// 客户端调用 chdir() 函数
#define LLS    5		// 列出客户端当前路径所有文件
#define CD     6		// 云端调用 chdir() 函数
#define PUT    7		// 上传文件到云端
#define QUIT   8		// 退出 FTP
#define DOFILE 0		// 标记

struct Msg{
    
    
	int type;
	char cmd[1024];
	char buf[1024];
}msg;

int get_cmd_type(char *cmd)
{
    
    
	if(strcmp("ls",cmd) == 0)	return LS;
	if(strcmp("quit",cmd) == 0)	return QUIT;
	if(strcmp("pwd",cmd) == 0)	return PWD;
	if(strstr(cmd,"cd")!=NULL)	return CD;
	if(strstr(cmd,"get")!=NULL)	return GET;
	if(strstr(cmd,"put")!=NULL)	return PUT;
}

char *getDesDir(char *cmd)
{
    
    
	char *p;
	p = strtok(cmd," ");
	p = strtok(NULL," ");
	return p;
}

void determine(struct Msg msg, int c_fd)
{
    
    
	char *file = NULL;
	int fd;

	printf("cmd:%s\n",msg.cmd);

	int ret = get_cmd_type(msg.cmd);

	switch(ret){
    
    
		case LS:
		case PWD:
			msg.type = 0;
			FILE *r = popen(msg.cmd,"r");
			fread(msg.buf,sizeof(msg.buf),1,r);
			write(c_fd,&msg,sizeof(msg));
			break;
		case CD:
			msg.type = 0;
			char *dir = getDesDir(msg.cmd);
			chdir(dir);
			break;
		case GET:
			file = getDesDir(msg.cmd);
			if(access(file,F_OK) == -1){
    
    
				strcpy(msg.cmd,"Without This File!");
				write(c_fd,&msg,sizeof(msg));
			}else{
    
    
				msg.type = DOFILE;
				fd = open(file,O_RDWR);
				read(fd,&msg.buf,sizeof(msg.buf));
				close(fd);
				write(c_fd,&msg,sizeof(msg));
			}
			break;
		case PUT:
			file = getDesDir(msg.cmd);
			fd = open(file,O_RDWR|O_CREAT,0666);
			write(fd,msg.buf,strlen(msg.buf));
			close(fd);
			break;
		case QUIT:
			printf("client quit!\n");
			exit(-1);
	}
}

void main(int argc, char **argv)
{
    
    
	int s_fd;
	int c_fd;
	int n_read;

	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;

	int clen = sizeof(struct sockaddr_in);

	if(argc!=3){
    
    
		printf("incorrect number of parameters\n");
		exit(-1);	
	}

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

	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);

	if(bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in)) == -1){
    
    
		perror("bind");
		exit(-1);
	}

	if(listen(s_fd,10) == -1){
    
    
		perror("listen");
		exit(-1);
	}

	while(1){
    
    
		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){
    
    
			while(1){
    
    
				memset(msg.cmd,0,sizeof(msg.cmd));
				read(c_fd,&msg,sizeof(msg));
				determine(msg,c_fd);
			}
		}
	}

}
Client-side:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/socket.h>
//#include<linux/in.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>

#define LS     1		// 列出云端所有文件夹
#define PWD    2		// 云端的当前路径
#define GET    3		// 获取云端文件
#define LCD    4		// 客户端调用 chdir() 函数
#define LLS    5		// 列出客户端当前路径所有文件
#define CD     6		// 云端调用 chdir() 函数
#define PUT    7		// 上传文件到云端
#define QUIT   8		// 退出 FTP
#define DOFILE 0		// 标记

struct Msg{
    
    
        int type;
        char cmd[1024];
        char buf[1024];
};

int get_cmd_type(char *cmd)
{
    
    
        if(strcmp("ls",cmd) == 0)       return LS;
	if(strcmp("lls",cmd) == 0)	return LLS;
        if(strcmp("quit",cmd) == 0)     return QUIT;
        if(strcmp("pwd",cmd) == 0)      return PWD;
	if(strstr(cmd,"lcd")!=NULL)	return LCD;
        if(strstr(cmd,"cd")!=NULL)      return CD;
        if(strstr(cmd,"get")!=NULL)     return GET;
        if(strstr(cmd,"put")!=NULL)     return PUT;
        return -1;
}

char *getDesDir(char *cmd)
{
    
    
	char *p;
	p = strtok(cmd," ");
	p = strtok(NULL," ");
	return p;
}

int determine(struct Msg msg, int c_fd)
{
    
    
	int fd;
	char *file = NULL;

	int ret = get_cmd_type(msg.cmd);

	switch(ret){
    
    
		case LS:
		case PWD:
			write(c_fd,&msg,sizeof(msg));
			read(c_fd,&msg,sizeof(msg));
			printf("--------------------\n\n");
			printf("%s\n",msg.buf);
			printf("\n--------------------\n");
			break;
		case LLS:
			printf("--------------------\n\n");
			system("ls");
			printf("\n--------------------\n");
			break;
		case CD:
			write(c_fd,&msg,sizeof(msg));
			break;
		case LCD:
			file = getDesDir(msg.cmd);
			chdir(file);
			break;
		case GET:
			write(c_fd,&msg,sizeof(msg));
			file = getDesDir(msg.cmd);
			read(c_fd,&msg,sizeof(msg));
			if(msg.type == DOFILE){
    
    
				fd = open(file,O_RDWR|O_CREAT,0666);
				write(fd,msg.buf,strlen(msg.buf));
				close(fd);
			}else{
    
    
				printf("%s\n",msg.cmd);
			}
			break;
		case PUT:
			strcpy(msg.buf,msg.cmd);
			file = getDesDir(msg.cmd);
			strcpy(msg.cmd,msg.buf);
			if(access(file,F_OK) == -1){
    
    
				printf("Without This File!\n");
                        }else{
    
    
				fd = open(file,O_RDWR);
				read(fd,msg.buf,sizeof(msg.buf));
				close(fd);
				write(c_fd,&msg,sizeof(msg));
                        }
                        break;
		case QUIT:
			write(c_fd,&msg,sizeof(msg));
			printf("Goodbye!\n");
			exit(-1);
	}
	return ret;
}

void main(int argc, char **argv)
{
    
    
	int c_fd;
	int n_read;
	struct Msg msg;

	struct sockaddr_in c_addr;

	if(argc!=3){
    
    
                printf("incorrect number of parameters\n");
                exit(-1);
        }

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

	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);

	if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
    
    
		perror("connect");
		exit(-1);
	}
	
	printf("connect success!\n");

	while(1){
    
    
		memset(msg.cmd,0,sizeof(msg.cmd));

		printf(">");

		gets(msg.cmd);

		if(strlen(msg.cmd) == 0){
    
    
			continue;
		}

		int ret = determine(msg,c_fd);

		if(ret == -1){
    
    
			printf("command not found\n");
			continue;
		}
	}
}

猜你喜欢

转载自blog.csdn.net/lcx1837/article/details/108080728