1. 程式人生 > >Linux C高階程式設計——網路程式設計之API(5)

Linux C高階程式設計——網路程式設計之API(5)

Linux C網路程式設計——API

宗旨:技術的學習是有限的,分享的精神的無限的。

一、基本socket函式

        Linux系統是通過提供套接字(socket)來進行網路程式設計的。網路的socket資料傳輸是一種特殊的I/O,socket也是一種檔案描述符。socket也有一個類似於開啟檔案的函式:socket(),呼叫socket(),該函式返回一個整型的socket的描述符,隨後的連線建立、資料傳輸等操作也都是通過該socket實現。

1socket函式

       ——建立套接字

(1)函式原型

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

(2)函式引數

        domain指明所使用的協議族,通常為PF_INET,表示TCP/IP協議;

        type引數指定socket的型別,基本上有三種:資料流套接字、資料報套接字、原始套接字

        protocol通常賦值"0"。

(3)返回值

        呼叫成功,返回socket檔案描述符;失敗,返回-1,並設定errno

        兩個網路程式之間的一個網路連線包括五種資訊:通訊協議、本地協議地址、本地主機埠、遠端主機地址和遠端協議埠。socket資料結構中包含這五種資訊。

2bind函式

——將套接字和指定的埠相連

(1)函式原型

struct sockaddr_in
{
    short intsin_family;
    unsignedshort int sin_port;
    structin_addr sin_addr;
    unsignedchar sin_zero[8];
}; // addrlen為sockaddr的長度。

int bind(int sock_fd, struct sockaddr_in *my_addr, int addrlen);

(2)函式引數

        sock_fd是呼叫socket函式返回值;

        my_addr是一個指向包含有本機IP地址及埠號等資訊的sockaddr型別的指標;

  struct sockaddr_in結構型別是用來儲存socket資訊的。

(3)返回值

        成功返回0,否則,返回-1,並置errno。  

3connect函式

——客戶端傳送服務請求

(1)函式原型

        int connect(int sock_fd, struct sockaddr*serv_addr,int addrlen);

(2)函式引數

        sock_fd 是socket函式返回的socket描述符;

        serv_addr是包含遠端主機IP地址和埠號的指標;

        addrlen是結構sockaddr_in的長度。

(3)返回值

        成功返回0,否則返回-1,並置errno。

4listen函式

        ——等待指定的埠的出現客戶端連線

(1)函式原型

        int listen(int sock_fd, int backlog);

(2)函式原型

        sock_fd 是socket()函式返回值;

        backlog指定在請求佇列中允許的最大請求數

(3)返回值

        呼叫成功返回0,否則,返回-1,並置errno.

5accecpt函式

       ——用於接受客戶端的服務請求

(1)函式原型

        int accept(int sock_fd, struct sockadd_in* addr, int addrlen);

(2)函式引數

        sock_fd是被監聽的socket描述符,

        addr通常是一個指向sockaddr_in變數的指標,

        addrlen是結構sockaddr_in的長度。

(3)返回值

        成功返回新的套接字描述符,失敗返回-1,並置errno。

6write函式

        ——write函式將buf中的nbytes位元組內容寫入檔案描述符

(1)函式原型

        ssize_t write(int fd,const void *buf,size_t nbytes);

(2)函式引數

       fd:套接字

       buf:要寫入的資料

       nbytes:位元組數

(3)返回值   

        成功時返回寫的位元組數.失敗時返回-1. 並設定errno變數.

在網路程式中,當我們向套接字檔案描述符寫時有倆種可能:

 (1)write的返回值大於0,表示寫了部分或者是全部的資料.

(2)返回的值小於0,此時出現了錯誤.需要根據錯誤型別來處理.  如果錯誤為EINTR表示在寫的時候出現了中斷錯誤.  如果錯誤為EPIPE表示網路連接出現了問題.

7read函式

       ——read函式是負責從fd中讀取內容

(1)函式原型

        ssize_t read(int fd,void *buf,size_t nbyte)

(2)函式引數

       fd:套接字

       buf:存放要讀出的資料

       nbytes:位元組數

(3)返回值

        當讀成功時,read返回實際所讀的位元組數,如果返回的值是0 表示已經讀到檔案的結束了,小於0表示出現了錯誤。如果錯誤為EINTR說明讀是由中斷引起的,如果錯誤是ECONNREST表示網路連接出了問題。

8close函式

        ——關閉socket

(1)函式原型

        int close(sock_fd);

(2)函式引數

        sock_fd:套接字

(3)返回值

        函式執行成功返回0,否則返回-1。

二、socket程式設計的其他函式說明

1網路位元組順序及其轉換函式

1網路位元組順序

         每一臺機器內部對變數的位元組儲存順序不同,而網路傳輸的資料是一定要統一順序的。所以對內部位元組表示順序與網路位元組順序不同的機器,一定要對資料進行轉換,從程式的可移植性要求來講,就算本機的內部位元組表示順序與網路位元組順序相同也應該在傳輸資料以前先呼叫資料轉換函式,以便程式移植到其它機器上後能正確執行。真正轉換還是不轉換是由系統函式自己來決定的。

2有關的轉換函式

unsigned short int htons(unsigned short int hostshort):主機位元組順序轉換成網路位元組順序,對無符號短型進行操作4bytes

unsigned long int htonl(unsigned long int hostlong):主機位元組順序轉換成網路位元組順序,對無符號長型進行操作8bytes

unsigned short int ntohs(unsigned short int netshort):網路位元組順序轉換成主機位元組順序,對無符號短型進行操作4bytes

unsigned long int ntohl(unsigned long int netlong):網路位元組順序轉換成主機位元組順序,對無符號長型進行操作8bytes

注:以上函式原型定義在netinet/in.h裡


2IP地址轉換

        有三個函式將數字點形式表示的字串IP地址與32位網路位元組順序的二進位制形式的IP地址進行轉換

(1)unsigned long int inet_addr(constchar * cp):該函式把一個用數字和點表示的IP地址的字串轉換成一個無符號長整型,如:struct sockaddr_in ina

ina.sin_addr.s_addr=inet_addr("202.206.17.101")

該函式成功時:返回轉換結果;失敗時返回常量INADDR_NONE,該常量=-1,二進位制的無符號整數-1相當於255.255.255.255,這是一個廣播地址,所以在程式中呼叫iner_addr()時,一定要人為地對呼叫失敗進行處理。由於該函式不能處理廣播地址,所以在程式中應該使用函式inet_aton()。

(2)int inet_aton(const char * cp,struct in_addr *inp):此函式將字串形式的IP地址轉換成二進位制形式的IP地址;成功時返回1,否則返回0,轉換後的IP地址儲存在引數inp中。

(3)char * inet_ntoa(struct in-addr in):將32位二進位制形式的IP地址轉換為數字點形式的IP地址,結果在函式返回值中返回,返回的是一個指向字串的指標。

3、位元組處理函式

        Socket地址是多位元組資料,不是以空字元結尾的,這和C語言中的字串是不同的。Linux提供了兩組函式來處理多位元組資料,一組以b(byte)開頭,是和BSD系統相容的函式,另一組以mem(記憶體)開頭,是ANSI C提供的函式。

以b開頭的函式有:

(1)void bzero(void * s,int n):將引數s指定的記憶體的前n個位元組設定為0,通常它用來將套接字地址清0。

(2)void bcopy(const void * src,void * dest,int n):從引數src指定的記憶體區域拷貝指定數目的位元組內容到引數dest指定的記憶體區域。

(3)int bcmp(const void * s1,const void * s2,int n):比較引數s1指定的記憶體區域和引數s2指定的記憶體區域的前n個位元組內容,如果相同則返回0,否則返回非0。

注:以上函式的原型定義在strings.h中。

以mem開頭的函式有:

(1)void * memset(void * s,int c,size_t n):將引數s指定的記憶體區域的前n個位元組設定為引數c的內容。

(2)void * memcpy(void * dest,const void * src,size_t n):功能同bcopy(),區別:函式bcopy()能處理引數src和引數dest所指定的區域有重疊的情況,memcpy()則不能。

(4)int memcmp(const void * s1,const void * s2,size_t n):比較引數s1和引數s2指定區域的前n個位元組內容,如果相同則返回0,否則返回非0。

注:以上函式的原型定義在string.h中。

三、程式說明

        本使用tcp協議進行通訊,服務端進行監聽,在收到客戶端的連線後,傳送資料給客戶端;客戶端在接受到資料後打印出來,然後關閉。

服務端:socket -- bind -- listen -- accept -- read/write -- close

客戶端:socket -- connect -- read/write -- close

/*************************************************************************
    > File Name: client.c
    > Author: libang
    > Mail: [email protected] 
    > Created Time: 2014年04月03日 星期日 02時59分37秒
 ************************************************************************/

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

#define MAXBUF 1024

int main(int argc,char *argv[])
{
	pid_t pid;
	int socket_fd,len;
	struct sockaddr_in dest;
	char buffer[MAXBUF];
	if(argc != 3)
	{
		printf("error format,it must be:\n\t\t%s IP port\n",argv[0]);
		exit(EXIT_FAILURE);
	}

	if((socket_fd = socket(AF_INET,SOCK_STREAM,0)) < 0)
	{
		perror("socket");
		exit(EXIT_FAILURE);
	}
	printf("Socket created!\n");
	bzero(&dest,sizeof(dest));
	dest.sin_family = AF_INET;
	dest.sin_port = htons(atoi(argv[2]));

	if(inet_aton(argv[1],(struct in_addr *)&dest.sin_addr.s_addr) == 0)
	{
		perror(argv[1]);
		exit(errno);
	}
	if(connect(socket_fd,(struct sockaddr *)&dest,sizeof(dest)) == -1)
	{
		perror("connect");
		exit(errno);
	}

	printf("Server connected!\n");

	if(-1 == (pid = fork()))
	{
		perror("fork");
		exit(EXIT_FAILURE);
	}
	else if(pid == 0)
	{
		while(1)
		{
			bzero(buffer,MAXBUF);
			len = recv(socket_fd,buffer,MAXBUF,0);
			if(len > 0)
			{
				printf("recv successful:'%s',%d byte recv\n",buffer,len);
			}
			else if(len < 0)
			{
				perror("recv");
				break;
			}
			else
			{
				printf("the other one close,quit\n");
				break;
			}
		}
	}

	else
	{
		while(1)
		{
			bzero(buffer,MAXBUF);
			//printf("please send message to send:");
			fgets(buffer,MAXBUF,stdin);
			if(!strncasecmp(buffer,"quit",4))
			{
				printf("I will quit!\n");
				break;
			}
			len = send(socket_fd,buffer,strlen(buffer) - 1,0);
			if(len < 0)
			{
				perror("send");
				break;
			}
		}
	}

	close(socket_fd);
	return 0;
}
/*************************************************************************
    > File Name: server.c
    > Author: libang
    > Mail: [email protected] 
    > Created Time: 2016年04月03日 星期日 20時57分48秒
 ************************************************************************/

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

#define MAXBUF 1024

int main(int argc,char *argv[])
{
	int socket_fd,new_fd;
	socklen_t len;
	struct sockaddr_in my_addr,their_addr;
	pid_t pid;
	unsigned int myport,list_num;
	char buf[MAXBUF];

	if(argv[2])
	{
		myport = atoi(argv[2]);
	}
	else
		myport = 7575;

	if(argv[3])
	{
		list_num = atoi(argv[3]);
	}
	else
		list_num = 10;
	
	if((socket_fd = socket(AF_INET,SOCK_STREAM,0)) == -1)
	{
		perror("socket");
		exit(EXIT_FAILURE);
	}

	bzero(&my_addr,sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(myport);

	if(argv[1])
	{
		my_addr.sin_addr.s_addr = inet_addr(argv[1]);
	}
	else
		my_addr.sin_addr.s_addr = INADDR_ANY;
	
	len = sizeof(struct sockaddr);
	if(bind(socket_fd,(struct sockaddr *)&my_addr,len) == -1)
	{
		perror("bind");
		exit(EXIT_FAILURE);
	}

	if(listen(socket_fd,list_num) == -1)
	{
		perror("listen");
		exit(EXIT_FAILURE);
	}
	printf("wait for connect!\n");

	if((new_fd = accept(socket_fd,(struct sockaddr *)&their_addr,&len)) == -1)
	{
		perror("accept");
		exit(EXIT_FAILURE);
	}
	else
		printf("server:got connection from %s,port %d,socket %d\n",inet_ntoa(their_addr.sin_addr),ntohs(their_addr.sin_port),new_fd);
	
	if(-1 == (pid = fork()))
	{
		perror("fork");
		exit(EXIT_FAILURE);
	}
	else if(pid == 0)
	{
		while(1)
		{
			bzero(buf,MAXBUF);
			//printf("input the message to send:");
			fgets(buf,MAXBUF,stdin);
			if(!strncasecmp(buf,"quit",4))
			{
				printf("I will close the connect!\n");
				break;
			}

			len = send(new_fd,buf,strlen(buf) - 1,0);
			if(len < 0)
			{
				printf("message '%s' send failure!error code is %d error message is '%s'\n",buf,errno,strerror(errno));
				break;
			}
		}
	}

	else
	{
		while(1)
		{
			bzero(buf,MAXBUF);
			len = recv(new_fd,buf,MAXBUF,0);
			if(len > 0)
			{
				printf("message recv successful!:'%s',%dByte recv\n",buf,len);

			}
			else if(len < 0)
			{
				printf("recv failure!errno code is %d,errno message is ‘%s\n",errno,strerror(errno));
				break;
			}
			else
			{
				printf("the other one close quit!\n");
				break;
			}
		}
	}

	close(new_fd);
	close(socket_fd);
	return 0;
}
gcc -o server server.c

gcc -o client client.c 

./server 192.168.207.129 7575 5
./client 192.168.207.129 7575