03網絡編程基本模型及api的講解

TCP客戶/服務器模型

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

socket

socket函數
	包含頭文件<sys/socket.h>
	功能:創建一個套接字用於通信
	原型
	int socket(int domain, int type, int protocol);
	參數
	domain :指定通信協議族(protocol family)
	type:指定socket類型,流式套接字SOCK_STREAM,數據報套接字SOCK_DGRAM,原始套接字SOCK_RAW
	protocol :協議類型
	返回值:成功返回非負整數, 它與文件描述符類似,我們把它稱爲套接口描述字,簡稱套接字。失敗返回-1

bind

bind函數
	包含頭文件<sys/socket.h>
	功能:綁定一個本地地址到套接字
	原型
	int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	參數
	sockfd:socket函數返回的套接字
	addr:要綁定的地址
	addrlen:地址長度
	返回值:成功返回0,失敗返回-1

listen

listen函數
	一般來說,listen函數應該在調用socket和bind函數之後,調用函數accept之前調用。
	對於給定的監聽套接口,內核要維護兩個隊列:
	1、已由客戶發出併到達服務器,服務器正在等待完成相應的TCP三路握手過程
	2、已完成連接的隊列

在這裏插入圖片描述
在這裏插入圖片描述

accept

accept函數
	包含頭文件<sys/socket.h>
	功能:從已完成連接隊列返回第一個連接,如果已完成連接隊列爲空,則阻塞。
	原型
	int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	參數
	sockfd:服務器套接字
	addr:將返回對等方的套接字地址
	addrlen:返回對等方的套接字地址長度
	返回值:成功返回非負整數,失敗返回-1

connect

connect函數
	包含頭文件<sys/socket.h>
	功能:建立一個連接至addr所指定的套接字
	原型
	int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	參數
	sockfd:未連接套接字
	addr:要連接的套接字地址
	addrlen:第二個參數addr長度
	返回值:成功返回0,失敗返回-1

Socket API 中的地址複用

SO_REUSEADDR
	服務器端儘可能使用SO_REUSEADDR
	在綁定之前儘可能調用setsockopt來設置SO_REUSEADDR套接字選項。
使用SO_REUSEADDR選項可以使得不必等待TIME_WAIT狀態消失就可以重啓服務器


2client.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>


#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>


/*
IPV4國際套接字地址
struct sockaddr_in {
   sa_family_t    sin_family; // address family: AF_INET
   in_port_t      sin_port;   // port in network byte order
   struct in_addr sin_addr;   // internet address 
};

 //Internet address.
struct in_addr {
   uint32_t       s_addr;     // address in network byte order
};

長連接與短連接客戶端說的算!
*/

//創建一個TCP協議的客戶端
//長連接
#if 0
void test()
{
	int sockfd = 0;
	const char *serverip = "192.168.66.128";
	
	//創建socket
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1)
	{
		perror("socket()");
		exit(0);
	}
	//定義socket結構體 man 7 ip
	
	struct sockaddr_in srvsddr;
	srvsddr.sin_family = AF_INET;
	srvsddr.sin_port = htons(8001);//轉化爲網絡字節序
	//第一種
	#if 0
	srvsddr.sin_addr.s_addr = inet_addr(serverip);
	#endif
	//第二種
	#if 0
	//srvsddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 就是0.0.0.0 不存在網絡字節序
	//srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY); //綁定本機的任意一個地址
	#endif
	//第三種
	//建議使用這種
	#if 1
	int ret;
	ret = inet_pton(AF_INET, serverip, &srvsddr.sin_addr);
	if (ret == 0)
	{
		printf("%s is invalid\n", serverip);
		return;
	}	
	#endif
	//進程-》內核
	if( connect(sockfd, (struct sockaddr *)&srvsddr,sizeof(srvsddr)) < 0)
	{
		perror("connect()");
		exit(0);
	}

	char recvbuf[1024] = {0};
	char sendbuf[1024] = {0};	
	//char *fgets(char *s, int size, FILE *stream); 從stream 讀取size-1大小的數據存入s,最後加'\0'
	while( fgets(sendbuf, sizeof(sendbuf), stdin) != NULL )
	{
		//向服務器寫數據
		//ssize_t write(int fd, const void *buf, size_t count);
		// 從buf中讀取count大小的數據存入文件描述符爲fd的文件中。
		write(sockfd, sendbuf, strlen(sendbuf));
		//ssize_t read(int fd, void *buf, size_t count);
		//從文件描述符爲fd的文件中讀取大小爲count的數據存入buf中。
		read(sockfd, recvbuf, sizeof(recvbuf));
		fputs(recvbuf, stdout);//從服務器收到數據,打印屏幕
		
		//清空緩衝區
		memset(recvbuf, 0, sizeof(recvbuf));
		memset(sendbuf, 0, sizeof(sendbuf));	
	}
	close(sockfd);
	return;	
}

#endif

//短連接
#if 1
void  test()
{

	int i = 0;
    //創建多個socket發送信息
	for (i = 0; i < 10; i++)
	{
		int sockfd = 0;
		sockfd = socket(PF_INET, SOCK_STREAM, 0);
		if (sockfd == -1)
		{
			perror("fun socket\n");
			exit(0);
		}

		struct sockaddr_in srvaddr;
		srvaddr.sin_family = AF_INET;
		srvaddr.sin_port = htons(8001);
		srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //127.0.0.1
		//srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY); //綁定本機的任意一個地址 

		if (connect(sockfd, (struct sockaddr*) (&srvaddr), sizeof(srvaddr)) < 0)
		{
			printf("errno:%d \n", errno);
			perror("fun socket\n");
			exit(0);
		}

		char revbuf[1024] = { 0 };
		char sendbuf[1024] = { 0 };
	
		sprintf(sendbuf, "i:%d\n", i);
		//向服務寫數據
		write(sockfd, sendbuf, strlen(sendbuf)); //服務器端回發信息
		//從服務器讀數據
		read(sockfd, revbuf, sizeof(revbuf));
		fputs(revbuf, stdout); //從服務器收到數據,打印屏幕

		memset(revbuf, 0, sizeof(revbuf));
		memset(sendbuf, 0, sizeof(sendbuf));
		
		close(sockfd);
	}
}
#endif

int main()
{
	test();
	return 0;
}

2server.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>


#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>


/*
IPV4國際套接字地址
struct sockaddr_in {
   sa_family_t    sin_family; // address family: AF_INET
   in_port_t      sin_port;   // port in network byte order
   struct in_addr sin_addr;   // internet address 
};

 //Internet address.
struct in_addr {
   uint32_t       s_addr;     // address in network byte order
};


*/

//創建一個TCP協議的服務器
#if 1
void test()
{
	int sockfd = 0;
	const char *serverip = "192.168.66.128";
	
	//創建socket
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1)
	{
		perror("socket()");
		exit(0);
	}
	//定義socket結構體 man 7 ip
	
	struct sockaddr_in srvsddr;
	srvsddr.sin_family = AF_INET;
	srvsddr.sin_port = htons(8001);//轉化爲網絡字節序
	//第一種
	#if 0
	srvsddr.sin_addr.s_addr = inet_addr(serverip);
	#endif
	//第二種
	#if 0
	//srvsddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 就是0.0.0.0 不存在網絡字節序
	//srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY); //綁定本機的任意一個地址
	#endif
	//第三種
	//建議使用這種
	#if 1
	int ret;
	ret = inet_pton(AF_INET, serverip, &srvsddr.sin_addr);
	if (ret == 0)
	{
		printf("%s is invalid\n", serverip);
		return;
	}	
	#endif
	
	//設置端口複用
	//使用SO_REUSEADDR選項可以使得不必等待TIME_WAIT狀態消失就可以重啓服務器
	int optval = 1;
    if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
	{
		perror("getsockopt()");
		exit(0);
	}
	
	//從進程->內核 將結構首地址和該結構大小都傳遞給了內核
	//內核知道需要從進程複製多少數據進來
	
	if(bind(sockfd, (struct sockaddr *)&srvsddr,sizeof(srvsddr)) <0 )
	{
		perror("bind()");
		exit(0);
	}
	//一但調用listen函數,這個套接字sockfd將變成被動套接字,只能接受連接,
	//不能主動的發送連接
	//listen 做了兩個隊列。。。。。。
	// 隊列由內核管理,一部分是完成三次握手的,一部分是沒有完成三次握手的。
	if(listen(sockfd, SOMAXCONN) < 0)
	{
		perror("listen()");
		exit(0);
	}
	
	/*
		值-結果參數:傳入結構體的首地址和結構體的長度,這樣內核
		在寫該結構不至於越界;當函數返回時,結構大小又是一個結果
		
	*/
	
	struct sockaddr_in peeraddr;
	unsigned int conn = 0;
	socklen_t peerlen = sizeof(peeraddr);//值-結果參數
	
	//內核到進程
	//accept接受已經完成三次握手的鏈接,沒有鏈接會阻塞直到有鏈接
	//sockfd監聽套接字 conn連接套接字
	conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
	if (-1 == conn) 
	{
		perror("accept()");
		exit(0);
	}

	printf("傳入後 peerlen = %d\n", peerlen);
	#if 1
	printf("客戶端的ip:%s port:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
	#endif
	
	#if 1
	char ip[16] = {0};
	socklen_t size = sizeof(ip);
	if (ip != NULL)
	{
		//inet_ntop(int af, const void *src,char *dst, socklen_t size)
		inet_ntop(AF_INET, (void *)&peeraddr.sin_addr.s_addr, (char *)ip, size);
	}
	if (errno == ENOSPC)
	{
		perror("分配的內存size不夠");
		return;
	}
	printf("客戶端的ip:%s port:%d\n", ip, ntohs(peeraddr.sin_port));
	
	#endif
	
	char recvbuf[1024] ={0};
	while(1)
	{
		memset(recvbuf, 0, sizeof(recvbuf));
		int ret = read(conn, recvbuf, sizeof(recvbuf));
		if (ret == 0) 
		{
			printf("對方已經關閉\n");
			exit(0);
		}
		else if (ret < 0)
		{
			perror("讀取數據失敗\n");
			exit(0);
		}
		fputs(recvbuf, stdout);//服務器收到數據,打印到屏幕
		write(conn, recvbuf, ret);	//將收到的數據再發給客戶端
	}
	
	return;	
}

#endif


int main()
{
	test();
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章