linux socket知識點記錄

client.c

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <pthread.h>
#include <sys/types.h>
#include <stdarg.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h> 
#include <dirent.h>
#include <signal.h>

void myprint(char* fmt, ...)
{
	char szPrintBuff[128] = {0};
	int iTmp = 0;
	va_list args;
	time_t now = time(NULL);
	struct tm * ltm = localtime(&now);
	
	iTmp = sprintf(szPrintBuff, "[%04d-%02d-%02d %02d:%02d:%02d]", ltm->tm_year + 1900, ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
	va_start(args, fmt);
	vsprintf(szPrintBuff + iTmp, fmt, args);
	va_end(args);
	
	fprintf(stdout,"%s", szPrintBuff);
}

#if 0
/*這個結構用來存儲套接字地址*/
struct sockaddr 
{
	unsigned short sa_family; /* address族, AF_xxx ,一般都是AFINET*/
	char sa_data[14]; /* 14 bytes的協議地址 包含了一些遠程電腦的地址、端口和套接字的數目,
						裏面的數據是雜的,所以有了sockaddr_in*/
};

/*爲了處理 struct sockaddr, 程序員建立了另外一個相似的大小相同的結構 struct sockaddr_in;
struct sockaddr_in (“in” 代表 “Internet”)*/
struct sockaddr_in 
{
	short int sin_family; /* Internet地址族 */
	unsigned short int sin_port; /* 端口號 網絡字節序 htons(),htonl(),ntohs(),ntohl()*/
	struct in_addr sin_addr; /* Internet地址 網絡字節序 inet_ntoa(sin_addr)*/
	unsigned char sin_zero[8]; /* 添0(和struct sockaddr一樣大小)*/
};
struct in_addr 
{
	unsigned long s_addr;/*inet_addr("166.111.69.52")*/
};
/*注意:inet_ntoa() 返回一個字符指針,它指向一個定義在函數 inet_ntoa() 中的 static 類型字符串。所
以每次你調用 inet_ntoa(),都會改變之前調用 inet_ntoa() 函數時得到的結果。*/
#endif

#define SERVER_PORT 12345
#define MAXDATASIZE 100
#define SERVER_IP "127.0.0.1"

/*
 1) SIGHUP	 	2) SIGINT	 	3) SIGQUIT	 	4) SIGILL	 	5) SIGTRAP
 6) SIGABRT	 	7) SIGBUS	 	8) SIGFPE	 	9) SIGKILL		10) SIGUSR1
11) SIGSEGV		12) SIGUSR2		13) SIGPIPE		14) SIGALRM		15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD		18) SIGCONT		19) SIGSTOP		20) SIGTSTP
21) SIGTTIN		22) SIGTTOU		23) SIGURG		24) SIGXCPU		25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF		28) SIGWINCH	29) SIGIO		30) SIGPWR
31) SIGSYS		34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX
*/
void handleSignal(int value)
{
	switch(value)
	{
		case 2:
			myprint("\n SIGINT : 點擊了Ctrl+C 關閉\n");
		break;
		case 3:
			myprint("\n SIGQUIT \n");
		break;
		case 8:
			myprint("\n SIGFPE : 浮點異常\n");
		break;
		case 9:
			myprint("\n SIGKILL \n");
		break;
		case 11:
			myprint("\n SIGSEGV : 段錯誤\n");
		break;
		case 13:
			myprint("\n SIGPIPE : 管道異常\n");
		break;
		case 15:
			myprint("\n SIGTERM : 被另一個進程關閉\n");
		break;
		default:
			myprint("\n Unknow type %d \n", value);
	}
} 

int main(int argc, char *argv[])
{
	int sockfd = -1;
	int recvbytesnum = 0;
	int sendbytesnum = 0;
	char recvbuf[MAXDATASIZE] = {0};
	char sendbuf[MAXDATASIZE] = {0};
	int port = 0;
	char ip[16] = {0};
	int run = 1;
	int run2 = 1;
	struct sockaddr_in server_addr = {0};
	int i = 0;
	int intoptval;
	int intlen = sizeof(int);
	
	if (argc != 3)
	{
		fprintf(stderr,"usage: client ip port\n");
		exit(1);
	}
	
	signal(SIGQUIT, handleSignal);
	signal(SIGKILL, handleSignal);
	signal(SIGFPE,  handleSignal);
	signal(SIGSEGV, handleSignal);
	signal(SIGTERM, handleSignal);
	signal(SIGABRT, handleSignal);
	signal(SIGBUS,  handleSignal);
	signal(SIGPIPE, handleSignal);//其中有一方退出,另一方就會收到此信號。具體過程可網上查
	
	strcpy(ip, argv[1]);
	port = atoi(argv[2]);
	/* 主機字節順序 */
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
#if 0
	/*比較新的函數,支持ipv4和ipv6,替代inet_aton
	p:表達(presentation)
	n:數值(numeric)
	*/
	inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); /* 可通過返回值判斷IP地址合法性 */
#else
	server_addr.sin_addr.s_addr = inet_addr(ip);
#endif
	//bzero(&(server_addr.sin_zero), 8);
	
	while(run)
	{
		sleep(5);
		myprint("connecting to %s:%d\n", ip, port);
		/*
		參數1:	協議或地址族,
				1、AF_INET:創建Network Socket;約等於PF_INET
				2、AF_UNIX:創建UNIX Domain Socket(一種IPC,用於進程間通信,不經過網卡),=AF_LOCAL
				3、AF_INET6:IPV6
				4、AF_CAN:
				5、AF_RDS:
		參數2:	服務的類型,
				1、SOCK_STREAM:TCP 
				2、SOCK_DGRAM:UDP
				3、SOCK_RAW:原始套接字,可以處理普通的套接字無法處理的ICMP、IGMP等網絡報文
				4、其他SOCK_常量之一
		參數3:	使用的協議號,一般爲0,即使用默認的TCP。
				或者在地址族爲AF_CAN的情況下,協議應爲CAN_RAW或CAN_BCM之一
				#define IPPROTO_IP 0  		dummy for IP 
				#define IPPROTO_ICMP 1  	control message protocol 
				#define IPPROTO_IGMP 2  	internet group management protocol 
				#define IPPROTO_GGP 3  		gateway^2 (deprecated) 
				#define IPPROTO_TCP 6  		tcp 
				#define IPPROTO_PUP 12  	pup 
				#define IPPROTO_UDP 17  	user datagram protocol 
				#define IPPROTO_IDP 22  	xns idp 
				#define IPPROTO_ND 77		UNOFFICIAL net disk proto 
				#define IPPROTO_RAW 255		raw IP packet 
				#define IPPROTO_MAX 256
		*/
		
		if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
		{
			myprint("ERR: create socket failed\n");
			continue;
		}
		
		/*
		參數2:	//定義於sys/socket.h
				SOL_SOCKET:Options to be accessed at socket level, not protocol level.通用套接字選項
				// 定義於netinet/in.h
				IPPROTO_IP: protocol.
				IPPROTO_IPV6:Internet Protocol Version 6.
				IPPROTO_ICMP:Control message protocol.
				IPPROTO_RAW:Raw IP Packets Protocol.
				IPPROTO_TCP:Transmission control protocol.
				IPPROTO_UDP:User datagram protocol.
		參數3:asm/socket.h
				SO_BROADCAST:允許發送廣播數據,僅UDP支持
				SO_DEBUG:允許調試,開啓跟蹤,僅TCP支持
				SO_DONTROUTE:旁路底層協議的正常路由機制
				SO_ERROR:獲得套接字錯誤並清除,僅GET用
				SO_KEEPALIVE:週期性測試連接是否存活
				SO_LINGER:若有數據,延遲關閉連接
				SO_OOBINLINE:帶外數據繼續存留
				SO_RCVBUF:接收緩衝區大小,對於客戶,O_RCVBUF選項必須在connect之前設置;對於服務器,SO_RCVBUF選項必須在listen前設置
				SO_SNDBUF: 發送緩衝區大小,
							如果在發送數據的時,希望不經歷由系統緩衝區到socket緩衝區的拷貝而影響程序的性能,可將發送緩衝區大小設置爲0。接收緩衝區同理
				SO_RCVLOWAT:接收緩衝區下限
				SO_SNDLOWAT:發送緩衝區下限
				SO_RCVTIMEO:接收超時,貌似阻塞狀態下有用
				SO_SNDTIMEO:發送超時,
				SO_REUSERADDR:允許重用本地地址和端口
				SO_TYPE:獲得套接字類型,僅GET用
				SO_BSDCOMPAT:路由套接口取得所有發送數據拷貝
				
				1.closesocket(一般不會立即關閉而經歷TIME_WAIT的過程)後想繼續重用該socket:
				BOOL bReuseaddr=TRUE;
				setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
				
				2. 如果要已經處於連接狀態的soket在調用closesocket後強制關閉,不經歷TIME_WAIT的過程:
				BOOL bDontLinger = FALSE;
				setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
				
				3.在send(),recv()過程中有時由於網絡狀況等原因,發收不能預期進行,而設置收發時限:
				struct timeval tv;
                tv.tv_sec = 10;
				tv.tv_usec = 0;
				//發送時限
				setsockopt(socket,SOL_SOCKET,SO_SNDTIMEO,(char *)&tv,sizeof(struct timeval));
				//接收時限
				setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&tv,sizeof(struct timeval));
				
				4.在send()的時候,返回的是實際發送出去的字節(同步)或發送到socket緩衝區的字節
				(異步);系統默認的狀態發送和接收一次爲8688字節(約爲8.5K);在實際的過程中發送數據
				和接收數據量比較大,可以設置socket緩衝區,而避免了send(),recv()不斷的循環收發:
				// 接收緩衝區
				int nRecvBuf=32*1024;//設置爲32K
				setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
				//發送緩衝區
				int nSendBuf=32*1024;//設置爲32K
				setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
				
				5. 如果在發送數據的時,希望不經歷由系統緩衝區到socket緩衝區的拷貝而影響
				程序的性能:
				int nZero=0;
				setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
				
				6.同上在recv()完成上述功能(默認情況是將socket緩衝區的內容拷貝到系統緩衝區):
				int nZero=0;
				setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
				
				7.一般在發送UDP數據報的時候,希望該socket發送的數據具有廣播特性:
				BOOL bBroadcast=TRUE;
				setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
				
				8.在client連接服務器過程中,如果處於非阻塞模式下的socket在connect()的過程中可
				以設置connect()延時,直到accpet()被呼叫(本函數設置只有在非阻塞的過程中有顯著的
				作用,在阻塞的函數調用中作用不大)
				BOOL bConditionalAccept=TRUE;
				setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
				
				9.如果在發送數據的過程中(send()沒有完成,還有數據沒發送)而調用了closesocket(),以前我們
				一般採取的措施是"從容關閉"shutdown(s,SD_BOTH),但是數據是肯定丟失了,如何讓沒發完的數據發送出去後關閉socket)?
				struct linger {
				u_short l_onoff;
				u_short l_linger;
				};
				linger m_sLinger;
				m_sLinger.l_onoff=1;//(在closesocket()調用,但是還有數據沒發送完畢的時候容許逗留)
				// 如果m_sLinger.l_onoff=0;則功能和2.)作用相同;
				m_sLinger.l_linger=5;//(容許逗留的時間爲5秒)
				setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
				
				10.
				SO_KEEPALIVE
				Setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
				實現在server端,如果幾次重傳keepalive ,客戶端沒有相應,刪除連接,釋放資源
				缺省是120分鐘,如果嫌保活週期太長,可自行設置。
				不是特別好用,還是自己實現保活邏輯吧。
				int                 keepIdle = 1000;
				int                 keepInterval = 10;
				int                 keepCount = 10;
				Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
				Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
				Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
		*/
		getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &intoptval, &intlen);
		printf("SO_RCVBUF = %d\n", intoptval);
		getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &intoptval, &intlen);
		printf("SO_SNDBUF = %d\n", intoptval);
		getsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &intoptval, &intlen);
		printf("SO_RCVTIMEO = %d\n", intoptval);
		getsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &intoptval, &intlen);
		printf("SO_SNDTIMEO = %d\n", intoptval);
		// intoptval = 0;
		// setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &intoptval, sizeof(intoptval));
		// getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &intoptval, &intlen);
		// printf("SO_RCVBUF = %d\n", intoptval);
		
		if(connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) 
		{
			myprint("ERR: connect failed\n");
			close(sockfd);
			continue;
		}
		while(run2)
		{
			
			if((recvbytesnum=recv(sockfd, recvbuf, MAXDATASIZE, 0)) == -1)
			{
				myprint("ERR: recv failed\n");
				break;
			}
			else
			{
				recvbuf[recvbytesnum] = '\0';
				myprint("recv msg: %s", recvbuf);
			}
			
			snprintf(sendbuf, sizeof(sendbuf), "hello %d", i);
			i++;
			sleep(5);
			if((sendbytesnum=send(sockfd, sendbuf, MAXDATASIZE, 0)) == -1)
			{
				myprint("ERR: send failed\n");
				break;
			}
			else
			{
				//myprint("send msg: %s", sendbuf);
			}
		}
		
		myprint("close socket\n");
		close(sockfd);
		
	}
		
	return 0;
}

server.c

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <pthread.h>
#include <sys/types.h>
#include <stdarg.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h> 
#include <dirent.h>
#include <signal.h>

void myprint(char* fmt, ...)
{
	char szPrintBuff[128] = {0};
	int iTmp = 0;
	va_list args;
	time_t now = time(NULL);
	struct tm * ltm = localtime(&now);
	
	iTmp = sprintf(szPrintBuff, "[%04d-%02d-%02d %02d:%02d:%02d]", ltm->tm_year + 1900, ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
	va_start(args, fmt);
	vsprintf(szPrintBuff + iTmp, fmt, args);
	va_end(args);
	
	fprintf(stdout,"%s", szPrintBuff);
}

#define SERVER_PORT 12345
#define LISTEN_BACKLOG 10
#define MAXDATASIZE 100

void handleSignal(int value)
{
	switch(value)
	{
		case 2:
			myprint("\n SIGINT : 點擊了Ctrl+C 關閉\n");
		break;
		case 3:
			myprint("\n SIGQUIT \n");
		break;
		case 8:
			myprint("\n SIGFPE : 浮點異常\n");
		break;
		case 9:
			myprint("\n SIGKILL \n");
		break;
		case 11:
			myprint("\n SIGSEGV : 段錯誤\n");
		break;
		case 13:
			myprint("\n SIGPIPE : 管道異常\n");
		break;
		case 15:
			myprint("\n SIGTERM : 被另一個進程關閉\n");
		break;
		default:
			myprint("\n Unknow type %d \n", value);
	}
} 

int main(int argc, char *argv[])
{
	struct sockaddr_in servsockaddr = {0};
	struct sockaddr_in clientsockaddr = {0};
	int listensockfd = -1;
	int connectsockfd = -1;
	int recvbytesnum = 0;
	int sendbytesnum = 0;
	char sendbuf[MAXDATASIZE] = "Welcome, client!\n";
	char recvbuf[MAXDATASIZE] = {0};
	int run = 1;
	int run2 = 1;
	int stsize = sizeof(struct sockaddr);
	
	if (argc != 2)
	{
		fprintf(stderr,"usage: server port\n");
		exit(1);
	}
	
	signal(SIGQUIT, handleSignal);
	signal(SIGKILL, handleSignal);
	signal(SIGFPE,  handleSignal);
	signal(SIGSEGV, handleSignal);
	signal(SIGTERM, handleSignal);
	signal(SIGABRT, handleSignal);
	signal(SIGBUS,  handleSignal);
	signal(SIGPIPE, handleSignal);

	if (-1 == (listensockfd = socket(AF_INET, SOCK_STREAM, 0)))
	{
		myprint("ERR: create socket failed\n");
		exit(1);
	}

	servsockaddr.sin_family = AF_INET;
	servsockaddr.sin_port = htons(atoi(argv[1]));
	servsockaddr.sin_addr.s_addr = INADDR_ANY;  /*本機任意一個IP*/
	//bzero(&(servsockaddr.sin_zero), 8);
	
	if (-1 == bind(listensockfd,(const struct sockaddr *)&servsockaddr, sizeof(servsockaddr))) 
	{
		myprint("ERR: bind failed\n");
		exit(1);
	}

	/*
	參數2:未經過處理的連接請求隊列可以容納的最大數目
	*/
	if (-1 == listen(listensockfd, LISTEN_BACKLOG)) 
	{
		myprint("ERR: listen failed\n");
		exit(1);
	}

	while (run) 
	{
		myprint("wait for client...\n");
		/*
		accept大致過程:
		1、有人從很遠很遠的地方嘗試調用 connect()來連接你的機器上的某個端口(當然是你已經在 listen()的)。
		2、他的連接將被 listen 加入等待隊列等待 accept()函數的調用(加入等待隊列的最多數目由調用 listen()函數的第二個參數 backlog 來決定)。
		3、你調用 accept()函數,告訴他你準備連接。
		4、accept()函數將回返回一個新的套接字描述符,這個描述符就代表了這個連接!
		*/
		if (-1 == (connectsockfd = accept(listensockfd, (struct sockaddr *)&clientsockaddr, &stsize)))
		{
			myprint("ERR: Accetp failed!");
			close(listensockfd);
			exit(1);
		}

		myprint("accept a client %s:%d\n", inet_ntoa(clientsockaddr.sin_addr), ntohs(clientsockaddr.sin_port));
		while(run2)
		{
			if((sendbytesnum=send(connectsockfd, sendbuf, MAXDATASIZE, 0)) == -1)
			{
				myprint("ERR: send failed\n");
				break;
			}
			else
			{
				//myprint("send msg: %s", sendbuf);
			}
			
			if((recvbytesnum=recv(connectsockfd, recvbuf, MAXDATASIZE, 0)) == -1)
			{
				myprint("ERR: recv failed\n");
				break;
			}
			else
			{
				recvbuf[recvbytesnum] = '\0';
				myprint("recv msg: %s\n", recvbuf);
				if(strcmp(recvbuf, "hello 5") == 0)
					break;
			}
		}

		myprint("close connect socket\n");
		/*
		調用 close()/closesocket() 函數意味着完全斷開連接(相關信息從內存中清除),不能發送數據也不能接收數據,是完全無法調用與數據收發有關的函數,如果想只斷開其中一條通道,可以用shutdown
		參數2:SHUT_RD:斷開輸入流。套接字無法接收數據(即使輸入緩衝區收到數據也被抹去),無法調用輸入相關函數。
					SHUT_WR:斷開輸出流。套接字無法發送數據,但如果輸出緩衝區中還有未傳輸的數據,則將傳遞到目標主機。
					SHUT_RDWR:同時斷開 I/O 流。相當於分兩次調用 shutdown(),其中一次以 SHUT_RD 爲參數,另一次以 SHUT_WR 爲參數。
		*/
		shutdown(connectsockfd, SHUT_RDWR);
		close(connectsockfd);
	}

	myprint("close listen socket\n");
	close(listensockfd);
	return 0;
}

待補充:阻塞非阻塞、select、其他網絡相關函數

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章