天天看點

Linux學習(二十二)網絡程式設計1、socket簡介2、IP位址轉換3、端口号4、位元組序5 網絡程式設計相關API6、典型C/S通信流程

對于網絡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>          

                        #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>          

                #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>          

                   #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>          

                      #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>          

                  #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通信過程如下

Linux學習(二十二)網絡程式設計1、socket簡介2、IP位址轉換3、端口号4、位元組序5 網絡程式設計相關API6、典型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;
}
           

執行結果: 伺服器

Linux學習(二十二)網絡程式設計1、socket簡介2、IP位址轉換3、端口号4、位元組序5 網絡程式設計相關API6、典型C/S通信流程

用戶端:

Linux學習(二十二)網絡程式設計1、socket簡介2、IP位址轉換3、端口号4、位元組序5 網絡程式設計相關API6、典型C/S通信流程

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

執行結果: 伺服器

Linux學習(二十二)網絡程式設計1、socket簡介2、IP位址轉換3、端口号4、位元組序5 網絡程式設計相關API6、典型C/S通信流程

用戶端

Linux學習(二十二)網絡程式設計1、socket簡介2、IP位址轉換3、端口号4、位元組序5 網絡程式設計相關API6、典型C/S通信流程

繼續閱讀