一篇文章拿捏网络编程

技术笔记!

一、网络基础

1.网络采用分层的思想:

(1)每一层实现不同的功能,对上层的数据做透明的传输;

(2)每一层向上层提供服务,同时使用下层提供的服务

2.体系结构模型:

3.  各层典型的协议:

3.1  网络接口和物理层

MAC地址:48为全球唯一,网络设备的身份标识

3.1.1  ARP/RARP:

        ARP:IP地址→MAC地址

        RARP:MAC地址→IP地址

3.1.2  PPP协议:

拨号协议(GPRS/3G/4G);

3.2  网络层:

        IP地址

3.3  传输层:
3.3.1  TCP的使用情况:

(1)适合于对传输质量要求较高,以及传输大量数据的通信;

(2)在需要可靠数据传输的场合,通常使用TCP协议;

(3)MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议;

3.3.2  UDP的使用情况:

(1)发送小尺寸数据(如对DNS服务器进行IP地址查询时);

(2)在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络);

(3)适合于广播/组播式通信中;

(4)MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议;

(5)流媒体、VODVoIPIPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输;

3.4  应用层:

4.  网络的封包和拆包:

4.1  TCP/IP协议通信模型
4.2  TCP/IP协议下的数据包

4.3  数据的封装与传递过程

5.  网络编程的预备知识

5.1  SOCKET

5.2  IP地址

5.2.1  IP地址分为IPV4和IPV6

        IPV4:采用32位的整数来表示;

        IPV6:采用128位整数来表示;

        mobileIPV6:  local IP(本地注册的IP),roam IP(漫游IP);

IPV4地址:

        点分形式:192.168.7.246

        32位整数:

特殊IP地址:

        局域网IP:192.XXX.XXX.XXX         10.XXX.XXX.XXX

        广播IP:XXX.XXX.XXX.255        255.255.255.255(全网广播)

        组播IP:224.XXX.XXX.XXX~239.XXX.XXX.XXX

5.3     端口号

        16位的数字(1-65535)

                众所周知端口:1~1023(FTP:21;        SSH:22;        HTTP:80;        HTTPS:469)

                保留端口:1024~5000(不建议使用)

                可以使用的端口:5000~65535

TCP端口和UDP端口时相互独立的;

网络里面的通信是由 IP地址+端口号 来决定;

5.4  字节序
5.4.1 大小端问题

字节序是指不同的CPU访问内存中的多字节数据的时候,存在大小端问题;

如果CPU访问的是字符串,则不存在大小端问题;

一般来说:

        X86/RAM:小端

        powerpc/mips,ARM作为路由器时,大端模式;

        网络传输的时候采用大端模式

============

        本地字节序、网络字节序:把给定系统所采用的字节序称为主机字节序。为了避免不同类别主机之间在数据交换时由于对于字节序的不同而导致的差错,引入了网络字节序。

5.4.2  字节序转换函数

5.4.3  IP地址转换函数

inet_pton():把本地的字符串形式的IP地址变成IPV4/IPV6的网络字节序的地址;

inet_ntop():和上面的相反。

二、TCP 编程  API

1.  网络编程常用函数

1. 1 socket() 创建套接字

 #include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

 1.1.1  参数:

        1.domain:    地址族      

        2.type:        套接字类型

        

        3.protocol: 一般填0,原始套接字编程时需要填充;

1.1.2  返回值

eg:
int fd = -1;
struct sockaddr_in sin;

/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
	perror ("socket");
	exit (1);
}
2.地址相关的数据结构
2.1  结构

如果时IPV6的编程,要使用struct sockaddr_in6结构体(详细 man  7 ipv6),通常更通用的方法可以通过struct sockaddr_storage来编程。
2.2  地址结构的一般用法

        

eg:
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT);	//网络字节序的端口号

3.  bind()        绑定本机地址和端口

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

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

3.1  参数:

        sockfd:通过socket()函数拿到的fd;

        addr:struct sockaddr的结构体变量的地址;

        

        addrLen:sizeof(struct sockaddr_in);

eg:
	/*优化1: 让服务器程序能绑定在任意的IP上 */
#if 1
	sin.sin_addr.s_addr = htonl (INADDR_ANY);
#else
	if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
		perror ("inet_pton");
		exit (1);
	}
#endif
/*2.2 绑定 */
if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
	perror ("bind");
	exit (1);
}
4.listen()把主动套接字变成被动套接字

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

 int listen(int sockfd, int backlog);

eg:
if (listen (fd, BACKLOG) < 0) {
	perror ("listen");
	exit (1);
}
printf ("Server starting....OK!\n");
5.accept()   阻塞等待客户端连接请求

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

5.1 参数

5.2  返回值

成功时返回已经建立好连接的新的newfd

eg:

	int newfd = -1;
#if 0
	newfd = accept (fd, NULL, NULL);
	if (newfd < 0) {
		perror ("accept");
		exit (1);
	}
#else
	/*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号 */
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof (cin);
	if ((newfd = accept (fd, (struct sockaddr *) &cin, &addrlen)) < 0) {
		perror ("accept");
		exit (1);
	}

	char ipv4_addr[16];
	if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) {
		perror ("inet_ntop");
		exit (1);
	}

	printf ("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs (cin.sin_port));

#endif
6.  读写

和newfd进行数据读写,通过read函数读取;

eg:

int ret = -1;
char buf[BUFSIZ];
while (1) {
	bzero (buf, BUFSIZ);
	do {
		ret = read (newfd, buf, BUFSIZ - 1);
	} while (ret < 0 && EINTR == errno);
	if (ret < 0) {

		perror ("read");
		exit (1);
	}
	if (!ret) {				//对方已经关闭
		break;
	}
	printf ("Receive data: %s\n", buf);

	if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
		printf ("Client is exiting!\n");
		break;
	}
}
7.  关闭

将文件描述符关闭:close

eg:

close (newfd);

close (fd);
8.  客户端的连接函数  connect()

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

8.1  参数:

8.2  返回值:

 If  the connection or binding succeeds, zero is returned.  On error, -1
       is returned, and errno is set appropriately.

eg:

	/*2.1 填充struct sockaddr_in结构体变量 */
	bzero (&sin, sizeof (sin));

	sin.sin_family = AF_INET;
	sin.sin_port = htons (SERV_PORT);	//网络字节序的端口号
#if 0
	sin.sin_addr.s_addr = inet_addr (SERV_IP_ADDR);
#else
	if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
		perror ("inet_pton");
		exit (1);
	}
#endif
	//通过connect函数连接服务器
	if (connect (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
		perror ("connect");
		exit (1);
	}

	printf ("Client staring...OK!\n");

INADDR_ANY:

是一个特殊的IP地址,用于绑定一个套接字(socket)到任意可用的本地IP地址。在网络编程中,通过将套接字绑定到INADDR_ANY,可以使得程序监听所有本地网络接口上的传入连接。这意味着程序可以接受任何远程IP地址的连接请求,而不仅仅是一个特定的IP地址。

9.  网络发送数据: send()/write()

 #include <sys/types.h>
 #include <sys/socket.h>

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

#include <unistd.h>

ssize write(int fd,const void* buf, size_t count);

send()比write()多一个参数flags

flags:

10.网络接收数据 read()/recv()

eg:

多进程:

server:

#include <pthread.h>
#include <signal.h>
#include "net.h"

void cli_data_handle (void *arg);

void sig_child_handle(int signo)
{
	if(SIGCHLD == signo) {
		waitpid(-1, NULL,  WNOHANG);
	}
}
int main (void)
{

	int fd = -1;
	struct sockaddr_in sin;
	
	signal(SIGCHLD, sig_child_handle);	

	/* 1. 创建socket fd */
	if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
		perror ("socket");
		exit (1);
	}

	/*优化4: 允许绑定地址快速重用 */
	int b_reuse = 1;
	setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));


	/*2. 绑定 */
	/*2.1 填充struct sockaddr_in结构体变量 */
	bzero (&sin, sizeof (sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons (SERV_PORT);	//网络字节序的端口号

	/*优化1: 让服务器程序能绑定在任意的IP上 */
#if 1
	sin.sin_addr.s_addr = htonl (INADDR_ANY);
#else
	if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
		perror ("inet_pton");
		exit (1);
	}
#endif
	/*2.2 绑定 */
	if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
		perror ("bind");
		exit (1);
	}

	/*3. 调用listen()把主动套接字变成被动套接字 */
	if (listen (fd, BACKLOG) < 0) {
		perror ("listen");
		exit (1);
	}
	printf ("Server starting....OK!\n");
	int newfd = -1;
	/*4. 阻塞等待客户端连接请求 */
	
        struct sockaddr_in cin;
        socklen_t addrlen = sizeof (cin);
	while(1) {
		pid_t pid = -1;
		if ((newfd = accept (fd, (struct sockaddr *) &cin, &addrlen)) < 0) {
                        perror ("accept");
                        break;
                }
		/*创建一个子进程用于处理已建立连接的客户的交互数据*/
		if((pid = fork()) < 0) {
			perror("fork");
			break;
		}
		
		if(0 == pid) {  //子进程中
			close(fd);
			char ipv4_addr[16];
                
			if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) {
                        	perror ("inet_ntop");
                        	exit (1);
               	 	}

               	 	printf ("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(cin.sin_port));	
			cli_data_handle(&newfd);		
			return 0;	
		
		} else { //实际上此处 pid >0, 父进程中 
			close(newfd);
		}
		

	}		


	close (fd);
	return 0;
}

void cli_data_handle (void *arg)
{
	int newfd = *(int *) arg;

	printf ("Child handling process: newfd =%d\n", newfd);

	//..和newfd进行数据读写
	int ret = -1;
	char buf[BUFSIZ];
	while (1) {
		bzero (buf, BUFSIZ);
		do {
			ret = read (newfd, buf, BUFSIZ - 1);
		} while (ret < 0 && EINTR == errno);
		if (ret < 0) {

			perror ("read");
			exit (1);
		}
		if (!ret) {				//对方已经关闭
			break;
		}
		printf ("Receive data: %s\n", buf);

		if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
			printf ("Client(fd=%d) is exiting!\n", newfd);
			break;
		}
	}
	close (newfd);

}

client 端:


/*./client serv_ip serv_port */
#include "net.h"

void usage (char *s)
{
	printf ("\n%s serv_ip serv_port", s);
	printf ("\n\t serv_ip: server ip address");
	printf ("\n\t serv_port: server port(>5000)\n\n");
}

int main (int argc, char **argv)
{
	int fd = -1;

	int port = -1;
	struct sockaddr_in sin;

	if (argc != 3) {
		usage (argv[0]);
		exit (1);
	}
	/* 1. 创建socket fd */
	if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
		perror ("socket");
		exit (1);
	}

	port = atoi (argv[2]);
	if (port < 5000) {
		usage (argv[0]);
		exit (1);
	}
	/*2.连接服务器 */

	/*2.1 填充struct sockaddr_in结构体变量 */
	bzero (&sin, sizeof (sin));

	sin.sin_family = AF_INET;
	sin.sin_port = htons (port);	//网络字节序的端口号
#if 0
	sin.sin_addr.s_addr = inet_addr (SERV_IP_ADDR);
#else
	if (inet_pton (AF_INET, argv[1], (void *) &sin.sin_addr) != 1) {
		perror ("inet_pton");
		exit (1);
	}
#endif

	if (connect (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
		perror ("connect");
		exit (1);
	}

	printf ("Client staring...OK!\n");
	/*3. 读写数据 */
	char buf[BUFSIZ];
	int ret = -1;
	while (1) {
		bzero (buf, BUFSIZ);
		if (fgets (buf, BUFSIZ - 1, stdin) == NULL) {
			continue;
		}
		do {
			ret = write (fd, buf, strlen (buf));
		} while (ret < 0 && EINTR == errno);

		if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
			printf ("Client is exiting!\n");
			break;
		}
	}

	/*4.关闭套接字 */
	close (fd);
}

多线程版:

server 端:

#include <pthread.h>
#include "net.h"

void cli_data_handle (void *arg);

int main (void)
{

	int fd = -1;
	struct sockaddr_in sin;

	/* 1. 创建socket fd */
	if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
		perror ("socket");
		exit (1);
	}

	/*优化4: 允许绑定地址快速重用 */
	int b_reuse = 1;
	setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));


	/*2. 绑定 */
	/*2.1 填充struct sockaddr_in结构体变量 */
	bzero (&sin, sizeof (sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons (SERV_PORT);	//网络字节序的端口号

	/*优化1: 让服务器程序能绑定在任意的IP上 */
#if 1
	sin.sin_addr.s_addr = htonl (INADDR_ANY);
#else
	if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
		perror ("inet_pton");
		exit (1);
	}
#endif
	/*2.2 绑定 */
	if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
		perror ("bind");
		exit (1);
	}

	/*3. 调用listen()把主动套接字变成被动套接字 */
	if (listen (fd, BACKLOG) < 0) {
		perror ("listen");
		exit (1);
	}
	printf ("Server starting....OK!\n");
	int newfd = -1;
	/*4. 阻塞等待客户端连接请求 */

/* 优化: 用多进程/多线程处理已经建立号连接的客户端数据 */
	pthread_t tid;

	struct sockaddr_in cin;
	socklen_t addrlen = sizeof (cin);

	while (1) {
		if ((newfd = accept (fd, (struct sockaddr *) &cin, &addrlen)) < 0) {
			perror ("accept");
			exit (1);
		}

		char ipv4_addr[16];
		if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) {
			perror ("inet_ntop");
			exit (1);
		}

		printf ("Clinet(%s:%d) is connected!\n", ipv4_addr, htons (cin.sin_port));

		pthread_create (&tid, NULL, (void *) cli_data_handle, (void *) &newfd);
	}

	close (fd);
	return 0;
}

void cli_data_handle (void *arg)
{
	int newfd = *(int *) arg;

	printf ("handler thread: newfd =%d\n", newfd);

	//..和newfd进行数据读写
	int ret = -1;
	char buf[BUFSIZ];
	while (1) {
		bzero (buf, BUFSIZ);
		do {
			ret = read (newfd, buf, BUFSIZ - 1);
		} while (ret < 0 && EINTR == errno);
		if (ret < 0) {

			perror ("read");
			exit (1);
		}
		if (!ret) {				//对方已经关闭
			break;
		}
		printf ("Receive data: %s\n", buf);

		if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
			printf ("Client(fd=%d) is exiting!\n", newfd);
			break;
		}
	}
	close (newfd);

}

client 端:


/*./client serv_ip serv_port */
#include "net.h"

void usage (char *s)
{
	printf ("\n%s serv_ip serv_port", s);
	printf ("\n\t serv_ip: server ip address");
	printf ("\n\t serv_port: server port(>5000)\n\n");
}

int main (int argc, char **argv)
{
	int fd = -1;

	int port = -1;
	struct sockaddr_in sin;

	if (argc != 3) {
		usage (argv[0]);
		exit (1);
	}
	/* 1. 创建socket fd */
	if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
		perror ("socket");
		exit (1);
	}

	port = atoi (argv[2]);
	if (port < 5000) {
		usage (argv[0]);
		exit (1);
	}
	/*2.连接服务器 */

	/*2.1 填充struct sockaddr_in结构体变量 */
	bzero (&sin, sizeof (sin));

	sin.sin_family = AF_INET;
	sin.sin_port = htons (port);	//网络字节序的端口号
#if 0
	sin.sin_addr.s_addr = inet_addr (SERV_IP_ADDR);
#else
	if (inet_pton (AF_INET, argv[1], (void *) &sin.sin_addr) != 1) {
		perror ("inet_pton");
		exit (1);
	}
#endif

	if (connect (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
		perror ("connect");
		exit (1);
	}

	printf ("Client staring...OK!\n");
	/*3. 读写数据 */
	char buf[BUFSIZ];
	int ret = -1;
	while (1) {
		bzero (buf, BUFSIZ);
		if (fgets (buf, BUFSIZ - 1, stdin) == NULL) {
			continue;
		}
		do {
			ret = write (fd, buf, strlen (buf));
		} while (ret < 0 && EINTR == errno);

		if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
			printf ("Client is exiting!\n");
			break;
		}
	}

	/*4.关闭套接字 */
	close (fd);
}

三、UDP编程 API

        udp是面向无连接不可靠传输,通常用于实时的音视频传输、DNS的域名解析包等。udp相较于tcp的连接是更简单的,服务端和客户端少了连接请求和监听步骤以及阻塞等待。

sendto()和recvfrom()函数:

 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

参数:sockfd,套接字文本描述符;

            buf:要发送的数据;

            len:  buf的长度;

             flags :通常为 0;

             dest_addr:要发送的目的地址;

             addrlen:地址长度;

返回值:失败时返回-1;

 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

参数:跟sendto()类似,地址是发送方的地址,可用地址结构体来接收;

返回值:失败时返回-1;

eg:

server端:

#include "net.h"

int main(void)
{
	int fd = -1;
 	struct sockaddr_in sin;
        
	/* 1. 创建socket fd */
        if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { //udp程序
                perror ("socket");
                exit (1);
        }

	/* 2. 允许绑定地址快速重用 */
        int b_reuse = 1;
        setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));

	        /*2. 绑定 */
        /*2.1 填充struct sockaddr_in结构体变量 */
        bzero (&sin, sizeof (sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons (SERV_PORT);       //网络字节序的端口号

        /* 让服务器程序能绑定在任意的IP上 */
#if 1
        sin.sin_addr.s_addr = htonl (INADDR_ANY);
#else
        if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
                perror ("inet_pton");
                exit (1);
        }
#endif
        /*2.2 绑定 */
        if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
                perror ("bind");
                exit (1);
        }
	
	char buf[BUFSIZ];
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);
	printf("\nUDP server started!\n");
	while(1) {
		bzero(buf, BUFSIZ);

		if( recvfrom(fd, buf, BUFSIZ-1, 0,(struct sockaddr *)&cin, &addrlen ) < 0) {
			perror("recvfrom");
			continue;
		}
		
		 char ipv4_addr[16];
                if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) {
                                perror ("inet_ntop");
                                exit (1);
 	        }

		printf("Recived from(%s:%d), data:%s",ipv4_addr, ntohs(cin.sin_port), buf);
		
		if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {  //用户输入了quit字符
                       printf ("Client(%s:%d) is exiting!\n", ipv4_addr, ntohs(cin.sin_port));
                }

	}

	close(fd);

	return 0;
}

client 端:

/*udp demo */
/* usage:
 * ./client serv_ip serv_port 
*/
#include "net.h"
void usage(char *s)
{
	printf("\nThis is udp demo!\n");
	printf("\nUsage:\n\t %s serv_ip serv_port",s);
	printf("\n\t serv_ip: udp server ip address");
	printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n");
}

int main(int argc, char *argv[])
{
	int fd = -1;
	int port = SERV_PORT;
	
	port = atoi(argv[2]);
	if(port < 0 || (port >0 && port <= 5000)) {
		usage(argv[0]);
		exit(1);
	}
        struct sockaddr_in sin;
	if(argc !=3) {
		usage(argv[0]);
		exit(1);
	}        

	/* 1. 创建socket fd*/
        if( (fd = socket(AF_INET,SOCK_DGRAM, 0)) < 0) { //UDP编程
                perror("socket");
                exit(1);
        }

	/*2.1 填充struct sockaddr_in结构体变量 */
        bzero(&sin,sizeof(sin));

        sin.sin_family = AF_INET;
        sin.sin_port = htons(SERV_PORT); //网络字节序的端口号
#if 0
        sin.sin_addr.s_addr = inet_addr(argv[1]);
#else
        if( inet_pton(AF_INET, argv[1], (void *)&sin.sin_addr) != 1) {
                perror("inet_pton");
                exit(1);
        }
#endif	
	printf("UDP client started!\n");
	char buf[BUFSIZ];
	while(1) {
		fprintf(stderr,"pls input string:");
		bzero(buf, BUFSIZ);
		if( fgets(buf, BUFSIZ-1, stdin) ==NULL) {
			perror("fgets");
			continue;
		}
		
		sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin)); 
		
		if( !strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))) {  //用户输入了quit字符
                        printf("Client is exited!\n");
                        break;
                }
	
	}
	close(fd);
	return 0;
}

头文件:

#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>                  /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>                 /* superset of previous */

#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.7.246"

#define QUIT_STR "quit"

#endif

四、IO模型和多路复用模型

1.  IO模型

1.1  IO模型的种类(主要有四种)
1.1.1  阻塞IO:最常用

1.1.2  非阻塞IO:可防止进程阻塞在IO操作上,需要轮询,消耗资源

1.1.3  IO多路复用:允许同时对多个IO进行控制

(1)通过select()/poll()实现多路复用

五、网络分析测试工具(wireshark)

六、IP和TCP头(常规大小20字节)

TCP/IP协议网络封包格式:

1.IP头(正常20字节,最大60字节)

version: IP版本(即IPV4/IPv6);

IHL:头部长度;

Type of Service:服务类型;

Total Length:总长;

Fragment Offset:碎片化和偏移量;

TTL:生存时间;

Protocol:协议;

Header Checksum:头部检验即CRC;

Source Address:源地址(4字节);

Destination Address:目的地址(4字节);

IP Option:IP 可选项;

2.TCP头(传输层:16位的端口)

source port:源端口(16位);

destination port:目的端口;

sequence number:序列号;

acknowlwdgement number: 确认序列号;

urgent pointer:  紧急指针;

window  :接收方允许发送方发送的数据量;

2.1  TCP的可靠传输:通过确认和重发机制

1.TCP把所有要发送的数据进行编号(每一个字节用一个号);

2.发送时从当前数据位置,发送window大小的数据;

2.2  TCP的面向连接(三次握手、四次挥手)

六、域名解析

1.  函数

#include <netdb.h>

struct hostent* gethostbyname(const char*  name);

参数: name  填写域名或IP地址;

返回值:成功时返回hostent 结构体指针;错误时返回NULL;

struct hostent {
               char  *h_name;            /* official name of host */
               char **h_aliases;         /* alias list */
               int    h_addrtype;        /* host address type */
               int    h_length;          /* length of address */
               char **h_addr_list;       /* list of addresses */
           }

void endhostent(void);                     //关闭域名解析

void herror(const char *s);                //域名解析中的使用herror()打印错误信息

eg:

七、网络属性的设置(setsockopt())

1.函数

 #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);
       int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

参数:

level指定控制套接字的层次,可以取三种:

(1)SOL_SOCKET:通用套接字选项     (应用层)

(2)IPPROTO_TCP:TCP选项 (传输层)

(3)IPPROTO_IP:IP选项        (网络层)

optname  选项

eg:

八、网络超时检测

1.设置socket的属性 SO_RCVTIMEO

参考代码如下
    struct timeval  tv;

     tv.tv_sec = 5;   //  设置5秒时间
     tv.tv_usec = 0;

     setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,  &tv,  
                       sizeof(tv));   //  设置接收超时
      recv() / recvfrom()    //   从socket读取数据
 

2.用select检测socket是否’ready’

参考代码如下
    struct fd_set rdfs;
    struct timeval  tv = {5 , 0};   // 设置5秒时间

    FD_ZERO(&rdfs);
    FD_SET(sockfd, &rdfs);

    if (select(sockfd+1, &rdfs, NULL, NULL, &tv) > 0)   // socket就绪
    {
          recv() /  recvfrom()    //  从socket读取数据
    }
3.设置定时器(timer), 捕捉SIGALRM信号

参考代码如下
    void  handler(int signo)     {   return;  }

      struct sigaction  act;
      sigaction(SIGALRM, NULL, &act);
      act.sa_handler = handler;
      act.sa_flags &= ~SA_RESTART;
      sigaction(SIGALRM, &act, NULL);
      alarm(5);
      if (recv(,,,) < 0) ……

4.心跳检测

(方法一):数据交互双方隔一段时间,一方发送一点数据到对方,对方给出特定的应答,如超过设定次数大小的时间内还是没有应答,这时候认为异常;

(方法二):改变套接字的属性来实现

九、广播和多播

单播方式只能发给一个接收方。

广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。

组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。

多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)

1.  广播(broadcast)

只有用户数据报 ( 使用 UDP 协议 ) 套接字才能广播

1.1  广播地址:
以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址
发到该地址的数据包被所有的主机接收
255.255.255.255在所有网段中都代表广播地址

要点:广播编程是基于UDP编程

        1.发送方要通过setsockopt设置套接字广播属性;

        2.发送方的发送地址为广播地址:XXX.XXX.XXX.255;

        3.接收方和发送方的端口号要相同;

eg:

发送:

2.  组播(IP_ADD_MEMBERSHIP)

2.1 组播地址:D类地址(组播地址)
不分网络地址和主机地址,第1字节的前4位固定为1110
224.0.0.1 – 239.255.255.255
 

eg:

struct ip_mreq
{
     struct  in_addr  imr_multiaddr;
     struct  in_addr  imr_interface;
};

struct  ip_mreq  mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(“235.10.10.3”);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);

setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,  
                  sizeof(mreq));

十、UNIX域套接字(unix domain)

用于本地进程间的通信。

1.域套接字的创建

使用本地协议PF_UNIX/PF_LOCAL;

socket(AF_LOCAL,SOCK_STREAM,0);

socket(AF_LOCAL,SOCK_DGRAM,0);

                服务器端                                                            客户端

 Address format
       A UNIX domain socket address is represented in the following structure:

           #define UNIX_PATH_MAX    108

           struct sockaddr_un {
               sa_family_t sun_family;               /* AF_UNIX */
               char        sun_path[UNIX_PATH_MAX];  /* pathname */
           };
在填充sockaddr_un结构体时,注意pathname是unix域套接字的文件路径(在内存中的文件)

(1)文件必须不存在

(2)一般给绝对路径

eg:

客户端


/*./client unix_domain_file */
#include "net.h"

void usage (char *s)
{
	printf ("\n%s unix_domain_file\n\n", s);
}

int main (int argc, char **argv)
{
	int fd = -1;

	int port = -1;
	struct sockaddr_un sun;

	if (argc != 2) {
		usage (argv[0]);
		exit (1);
	}
	/* 1. 创建socket fd */
	if ((fd = socket (AF_LOCAL, SOCK_STREAM, 0)) < 0) {
		perror ("socket");
		exit (1);
	}

	/*2.连接服务器 */

	/*2.1 填充struct sockaddr_un结构体变量 */
	bzero (&sun, sizeof (sun));

	sun.sun_family = AF_LOCAL;

	
	/*确保UNIX_DOMAIN_FILE要先存在并且可写,不存在则退出 */
	if( access(UNIX_DOMAIN_FILE, F_OK| W_OK) < 0){
		exit(1);
	}
	strncpy(sun.sun_path, UNIX_DOMAIN_FILE, strlen( UNIX_DOMAIN_FILE));//填充sun.sun_path路径


	if (connect (fd, (struct sockaddr *) &sun, sizeof (sun)) < 0) {
		perror ("connect
		exit (1);
	}

	printf ("Unix domain Client staring...OK!\n");

	int ret = -1;
	fd_set rset;
	int maxfd = -1;
	struct timeval tout;
	char buf[BUFSIZ];

	while (1) {
		FD_ZERO (&rset);
		FD_SET (0, &rset);
		FD_SET (fd, &rset);
		maxfd = fd;

		tout.tv_sec = 5;
		tout.tv_usec = 0;

		select (maxfd + 1, &rset, NULL, NULL, &tout);
		if (FD_ISSET (0, &rset)) {	//标准键盘上有输入
			//读取键盘输入,发送到网络套接字fd
			bzero (buf, BUFSIZ);
			do {
				ret = read (0, buf, BUFSIZ - 1);
			} while (ret < 0 && EINTR == errno);
			if (ret < 0) {
				perror ("read");
				continue;
			}
			if (!ret)
				continue;

			if (write (fd, buf, strlen (buf)) < 0) {
				perror ("write() to socket");
				continue;
			}

			if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
				printf ("Client is exiting!\n");
				break;
			}
		}

		if (FD_ISSET (fd, &rset)) {	//服务器给发送过来了数据
			//读取套接字数据,处理
			bzero (buf, BUFSIZ);
			do {
				ret = read (fd, buf, BUFSIZ - 1);
			} while (ret < 0 && EINTR == errno);
			if (ret < 0) {
				perror ("read from socket");
				continue;
			}
			if (!ret)
				break;			/* 服务器关闭 */

			//There is a BUG,FIXME!!
			printf ("server said: %s\n", buf);
			if ((strlen(buf) > strlen(SERV_RESP_STR)) 
				&& !strncasecmp (buf+strlen(SERV_RESP_STR), QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
				printf ("Sender Client is exiting!\n");
				break;
			}

		}
	}

	/*4.关闭套接字 */
	close (fd);
}

服务器端

#include <pthread.h>
#include <signal.h>
#include "net.h"

void cli_data_handle (void *arg);

void sig_child_handle(int signo)
{
	if(SIGCHLD == signo) {
		waitpid(-1, NULL,  WNOHANG);
	}
}
int main (void)
{

	int fd = -1;
	
	signal(SIGCHLD, sig_child_handle);	

	/* 1. 创建socket fd */
	if ((fd = socket (AF_LOCAL, SOCK_STREAM, 0)) < 0) { //基于本地的TCP通信
		perror ("socket");
		exit (1);
	}

	/* 允许绑定地址快速重用 */
	int b_reuse = 1;
	setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
	
	/* 2.1 填充sockaddr_un结构体变量 */
	struct sockaddr_un sun;
	bzero(&sun, sizeof(sun));
	sun.sun_family = AF_LOCAL;
	
	/* 如果UNIX_DOMAIN_FILE所指向的文件存在,则删除 */
	if(!access(UNIX_DOMAIN_FILE, F_OK)) {
		unlink(UNIX_DOMAIN_FILE);
	}
	strncpy(sun.sun_path, UNIX_DOMAIN_FILE, strlen( UNIX_DOMAIN_FILE));
	

	/*2.2 绑定 */
	if (bind (fd, (struct sockaddr *) &sun, sizeof (sun)) < 0) {
		perror ("bind");
		exit (1);
	}

	/*3. 调用listen()把主动套接字变成被动套接字 */
	if (listen (fd, BACKLOG) < 0) {
		perror ("listen");
		exit (1);
	}
	printf ("Unix domain server starting....OK!\n");
	int newfd = -1;
	/*4. 阻塞等待客户端连接请求 */
	while(1) {
		pid_t pid = -1;
		if ((newfd = accept (fd, NULL,NULL)) < 0) {
                        perror ("accept");
                        break;
                }
		/*创建一个子进程用于处理已建立连接的客户的交互数据*/
		if((pid = fork()) < 0) {
			perror("fork");
			break;
		}
		
		if(0 == pid) {  //子进程中
			close(fd);

               	 	printf ("Clinet is connected!\n");	
			cli_data_handle(&newfd);		
			return 0;	
		
		} else { //实际上此处 pid >0, 父进程中 
			close(newfd);
		}
		

	}		


	close (fd);
	return 0;
}

void cli_data_handle (void *arg)
{
	int newfd = *(int *) arg;

	printf ("Child handling process: newfd =%d\n", newfd);

	//..和newfd进行数据读写


	int ret = -1;
	char buf[BUFSIZ];
	char resp_buf[BUFSIZ+10];
	while (1) {
		bzero (buf, BUFSIZ);
		do {
			ret = read (newfd, buf, BUFSIZ - 1);
		} while (ret < 0 && EINTR == errno);
		if (ret < 0) {

			perror ("read");
			exit (1);
		}
		if (!ret) {				//对方已经关闭
			break;
		}
		printf ("Receive data: %s\n", buf);
		
		if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
			printf ("Client(fd=%d) is exiting!\n", newfd);
			break;
		}

		bzero(resp_buf, BUFSIZ+10);
		
		strncpy(resp_buf, SERV_RESP_STR, strlen(SERV_RESP_STR));
		strcat(resp_buf, buf); 	
		do {
			ret = write(newfd, resp_buf, strlen(resp_buf));	
		}while(ret < 0 && EINTR == errno);
		
	}
	close (newfd);

}

2.进程间的通信:

(1)进程间的数据共享

        管道、消息队列、共享内存、unix域套接字

易用性:消息队列 > unix域套接字 > 管道 > 共享内存(经常要和信号量一起用)

效率:共享内存 > unix套接字 > 管道 > 消息队列

常用: 共享内存、unix域套接字

(2)异步通信

        信号

(3)同步和互斥(做资源保护)

        信号量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值