Linux学习(二十二)网络编程

对于网络TCP/IP的相关知识,这里不做介绍,只介绍Linux中如何使用的问题,相关详细知识参考文档TCPIP相关知识

1、socket简介

        在 Linux学习(二十):进程间通信 中我们介绍过,
        常用的进程间通信方式
         1、传统的进程间通信方式

        无名管道(pipe)、有名管道(fifo)和信号(signal)

        2、System V IPC对象

        共享内存(share memory)、消息队列(message queue)和信号灯(semaphore)

        3、BSD

        套接字(socket)

套接字通信方式一般用于网络通信中,也就是在不同的PC之间通信(当然也可以用于本地通信)。

      1.1套接字的类型

      流式套接字(SOCK_STREAM)
        提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。TCP就属于此类
     数据报套接字(SOCK_DGRAM)
      提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收 UDP属于此类
    原始套接字(SOCK_RAW)
    可以对较低层次协议如 IP ICMP 直接访问。

2、IP地址转换

    我们常用的IP地址的形式是“192.168.0.1”这样的点分十进制,需要转换成对应的整形数据。

     in_addr_t inet_addr(const char *strptr);     //将字符串转化为二进制的值

     char*inet_ntoa(stuct in_addr inaddr);      //将32位数据转化位字符串的点分十进制

3、端口号

    为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别
    TCP端口号与UDP端口号独立
    端口号一般由IANA(Internet Assigned Numbers Authority) 管理
    众所周知端口: 1~1023 1~255 之间为众所周知端口, 256~1023 端口通常由 UNIX 系统占用)
    已登记端口: 1024~4999
    动态或私有端口 5000~65535

4、字节序

        不同类型CPU的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO):
        小端序(little-endian- 低序字节存储在低地址
       将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式;
        大端序(big-endian-高序字节存储在低地址
     将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola等所采用
      如何测试当前主机的字节序呢,参考 数据存储的大小端
     网络中传输的数据必须按网络字节序, 即大端字节序
     在大部分PC机上,当应用进程将整数送入socket前,需要转化成网络字节序;当应用进程从socket取出整数后,要转化成小端字节序。这是因为主机字节序可能和网络字节序的大小端不同。
        主机字节序与网络字节序的转化有以下几个函数
         主机字节序到网络字节序
        u_long htonl (u_long hostlong);
        u_short htons (u_short short);

         网络字节序到主机字节序
        u_long ntohl (u_long hostlong);
        u_short ntohs (u_short short);

5 网络编程相关API

5.1 创建socket

头文件:#include <sys/types.h>          /* See NOTES */
                        #include <sys/socket.h>
        函数原型    int socket(int domain, int type, int protocol);
功能:创建一个套接字,返回一个文件描述符
参数:
domain:通信域,协议族
AF_UNIX 本地通信
AF_INET 网络通信
AF_PACKET 底层的协议
type:类型
SOCK_STREAM 流式套接字 tcp
SOCK_DGRAM 数据报套接字 udp
SOCK_RAW 原始套接字
protocol:协议,一般为0
返回值:
成功:文件描述符
失败:-1
例程:
int sockfd;
		if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		{
			perror("fail to socket");
			//return -1;
			exit(1);
		}

5.2 绑定套接字bind

头文件: #include <sys/types.h>          /* See NOTES */
                #include <sys/socket.h>
函数原型: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:        将套接字与网络信息结构体绑定
参数:        1、sockfd:文件描述符,socket的返回值
                   2、addr:网络信息结构体,通用的格式如下
                                   struct sockaddr {
sa_family_t sa_family;  2个字节
char        sa_data[14]; 14个字节
     }
                                 但通用的这种格式我们一般不使用,因为使用起来不方便,我们一般使用下面这种格式(注意格                                 式强制转换):
                                struct sockaddr_in
                                {
                                   sa_family_t sin_family; 地址族 AF_INET 2个字节
                                   in_port_t sin_port;   端口号 2个字节
                                    struct in_addr sin_addr; 
                                }
                                 这个结构体的头文件位于#include <netinet/in.h>
                                 struct in_addr 的格式如下:
                                 struct in_addr                                                                                                   

in_addr_t s_addr;  //IP地址 4个字节
};
                     3、addrlen:addr的长度
返回值:
成功:0
失败:-1
例程:
	struct sockaddr_in serveraddr;
		
		serveraddr.sin_family = AF_INET;
		serveraddr.sin_port = htons(9999);
		serveraddr.sin_addr.s_addr = inet_addr("192.168.2.123");
		
		if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0)
		{
			perror("fail to bind");
			exit(1);
		}

5.3 监听套接字listen

头文件:   #include <sys/types.h>          /* See NOTES */
                   #include <sys/socket.h>
函数原型    int listen(int sockfd, int backlog);
功能:        将套接字设置为被动监听模式
参数:
sockfd:文件描述符,socket的返回值
backlog:允许同时响应客户端的连接请求的个数,一般为5, 10
返回值:
成功:0
失败:-1
例程:
if(listen(sockfd, 5) < 0)
		{
			perror("fail to listen");
			exit(1);
		}

5.4 接收套接字accept

头文件:        #include <sys/types.h>          /* See NOTES */
                      #include <sys/socket.h>
函数原型:    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:           阻塞等待客户端的连接请求
参数:
     sockfd:文件描述符,socket的返回值
     addr:网络信息结构体(被填充的获取到的客户端的网络信息结构体,同样使用struct sockaddr_in类                         型
     addrlen:addr的长度(注意此处为指针形式,bind中为类整形)
返回值:
成功:新的文件描述符(用于通信)
失败:-1
注意:accept的返回值是一个新的文件描述符,这个描述符是用于后面读写通信的,原来的文件描述符用于listen和accept中。

5.5 连接套接字connect

头文件:  #include <sys/types.h>          /* See NOTES */
                  #include <sys/socket.h>
函数原型:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:       发送客户端的连接请求
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体(自己填充的服务器的网络信息结构体,同样使用struct sockaddr_in类型
addrlen:addr的长度
返回值:
成功:0
失败:-1
例程:
if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0)
		{
			perror("fail to connect");
			exit(1);
		}

5.6 发送

头文件:     #include <sys/types.h>    #include <sys/socket.h>
函数原型:  ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:         发送数据(用于TCP通信)
参数:
     sockfd:文件描述符
     服务器:accept的返回值
     客户端:socket的返回值
     buf:发送的数据
     len:数据的长度
     flags:标志位
    0  阻塞
    MSG_DONTWAIT 非阻塞
返回值:
成功:发送的数据的字节数
失败:-1


头文件:  #include <sys/socket.h>
函数原型:ssize_t sendto(int socket, const void *message, size_t length,
                                           int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
功能:        发送数据(用于UDP通信)
参数:
socket:文件描述符
message:发送的数据
length:数据的长度
flags:标志位,一般为0
dest_addr:目的地址(发送给谁)
dest_len:addr的长度
返回值:
成功:发送的数据的字节数
失败:-1

5.7  读数据

头文件:        #include <sys/types.h>
                      #include <sys/socket.h>
函数原型:    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:           接收数据(用于TCP通信)
参数:
      sockfd:文件描述符
            服务器:accept的返回值
            客户端:socket的返回值
     buf:接收的数据
     len:数据的长度
     flags:标志位
0  阻塞
MSG_DONTWAIT 非阻塞
返回值:
成功:接收的数据的字节数
0  发送端文件描述符关闭
失败:-1
注意:在发送端关闭描述符时,接收到接收到0字节数据,这点要单独处理一下。

头文件:    #include <sys/types.h>
                  #include <sys/socket.h>
函数原型:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                                               struct sockaddr *src_addr, socklen_t *addrlen);
功能:         接收数据(用于UDP通信)
参数:
sockfd:文件描述符
buf:接收的数据
len:数据的长度
flags:标志位,一般为0
src_addr:源的地址(自动填充)
addrlen:addr的长度
返回值:
成功:接收的数据的字节数
失败:-1

6、典型C/S通信流程

典型的C/S通信过程如下


服务器端:
#include <stdio.h>  //printf
#include <arpa/inet.h>  //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h>  //socket bind listen accept connect
#include <netinet/in.h>  //sockaddr_in
#include <stdlib.h>  //exit
#include <unistd.h>  //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{\
							perror(errmsg);\
							printf("%s --> %s --> %d\n", __FILE__, __func__, __LINE__);\
							exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd, acceptfd;
	struct sockaddr_in serveraddr, clientaddr;
	socklen_t addrlen = sizeof(serveraddr);
	char buf[N] = {};
	ssize_t bytes;

	if(argc < 3)
	{
		printf("您输入的参数太少了: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器网络信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));

	//第三步:将套接字域网络信息结构体绑定
	if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
	{
		errlog("fail to bind");
	}

	//第四步:将套接字设置为监听状态
	if(listen(sockfd, 5) < 0)
	{
		errlog("fail to listen");
	}

	//第五步:阻塞等待客户端的连接请求
	if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) < 0)
	{
		errlog("fail to accept");
	}

	//打印客户端的ip地址、端口号
	printf("%s --- %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

	while(1)
	{
		if((bytes = recv(acceptfd, buf, N, 0)) < 0)
		{
			errlog("fail to recv");
		}
		else if(bytes == 0)
		{
			printf("NO DATA\n");
			exit(1);
		}
		else 
		{
			if(strncmp(buf, "quit", 4) == 0)
			{
				printf("client is quited ...\n");
				break;
			}
			else
			{
				printf("client : %s\n", buf);

				strcat(buf, " *_*");

				if(send(acceptfd, buf, N, 0) < 0)
				{
					errlog("fail to send");
				}
			}
		}
	}

	close(acceptfd);
	close(sockfd);
	
	return 0;
}

客户端例程
#include <stdio.h>  //printf
#include <arpa/inet.h>  //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h>  //socket bind listen accept connect
#include <netinet/in.h>  //sockaddr_in
#include <stdlib.h>  //exit
#include <unistd.h>  //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{\
							perror(errmsg);\
							printf("%s --> %s --> %d\n", __FILE__, __func__, __LINE__);\
							exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	char buf[N] = {};

	if(argc < 3)
	{
		printf("您输入的参数太少了: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器网络信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));

#if 0
	//客户端也可以自己指定自己的信息
	struct sockaddr_in clientaddr;
	clientaddr.sin_family = AF_INET;
	clientaddr.sin_addr.s_addr = inet_addr(argv[3]);
	clientaddr.sin_port = htons(atoi(argv[4]));

	if(bind(sockfd, (struct sockaddr *)&clientaddr, addrlen) < 0)
	{
		errlog("fail to bind");
	}
#endif

	//第三步:发送客户端的连接请求
	if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0)
	{
		errlog("fail to connect");
	}

	while(1)
	{
		fgets(buf, N, stdin);
		buf[strlen(buf) - 1] = '\0';

		if(send(sockfd, buf, N, 0) < 0)
		{
			errlog("fail to send");
		}

		if(strncmp(buf, "quit", 4) == 0)
		{
			printf("client quit ...\n");
			break;
		}
		else
		{
			if(recv(sockfd, buf, N, 0) < 0)
			{
				errlog("fail to recv");
			}

			printf("server : %s\n", buf);
		}
	}

	close(sockfd);
	
	return 0;
}

执行结果:
服务器

客户端:

127开头的IP地址表示主机地址

        对于UDP的通信来讲就简单多了,创建了socket之后,客户端就可以直接发送了(因为服务器的地址信息是已知的),服务器在接收到来自客户端的数据后,也就有了来自客户端的地址信息,服务也就可以向客户端发送数据了。
服务器例程:
#include <stdio.h>  //printf
#include <arpa/inet.h>  //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h>  //socket bind listen accept connect
#include <netinet/in.h>  //sockaddr_in
#include <stdlib.h>  //exit
#include <unistd.h>  //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{\
							perror(errmsg);\
							printf("%s --> %s --> %d\n", __FILE__, __func__, __LINE__);\
							exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in serveraddr, clientaddr;
	socklen_t addrlen = sizeof(serveraddr);
	char buf[N] = {};
	ssize_t bytes;

	if(argc < 3)
	{
		printf("您输入的参数太少了: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器网络信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));

	//第三步:将套接字域网络信息结构体绑定
	if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
	{
		errlog("fail to bind");
	}

	while(1)
	{
		if((bytes = recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&clientaddr, &addrlen)) < 0)
		{
			errlog("fail to recvfrom");
		}
		else if(bytes == 0)
		{
			printf("NO DATA\n");
			exit(1);
		}
		else 
		{
			//打印客户端的ip地址、端口号
			printf("%s --- %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

			if(strncmp(buf, "quit", 4) == 0)
			{
				printf("client is quited ...\n");
				break;
			}
			else
			{
				printf("client : %s\n", buf);

				strcat(buf, " *_*");

				if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&clientaddr, addrlen) < 0)
				{
					errlog("fail to sendto");
				}
			}
		}
	}

	close(sockfd);
	
	return 0;
}
客户端例程:
#include <stdio.h>  //printf
#include <arpa/inet.h>  //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h>  //socket bind listen accept connect
#include <netinet/in.h>  //sockaddr_in
#include <stdlib.h>  //exit
#include <unistd.h>  //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{\
							perror(errmsg);\
							printf("%s --> %s --> %d\n", __FILE__, __func__, __LINE__);\
							exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	char buf[N] = {};

	if(argc < 3)
	{
		printf("您输入的参数太少了: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器网络信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));

	while(1)
	{
		fgets(buf, N, stdin);
		buf[strlen(buf) - 1] = '\0';

		if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&serveraddr, addrlen) < 0)
		{
			errlog("fail to sendto");
		}

		if(strncmp(buf, "quit", 4) == 0)
		{
			printf("client quit ...\n");
			break;
		}
		else
		{
			//if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&serveraddr, &addrlen) < 0)
			if(recvfrom(sockfd, buf, N, 0, NULL, NULL) < 0)
			{
				errlog("fail to recvfrom");
			}

			printf("server : %s\n", buf);
		}
	}

	close(sockfd);
	
	return 0;
}
执行结果:
服务器
客户端








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值