1. 程式人生 > >伺服器、客戶端簡單互動程式

伺服器、客戶端簡單互動程式

        這是一個簡單的TCP伺服器/客戶端的程式示例。客戶端傳送兩個long型變數到伺服器端,伺服器端讀取這兩個long型變數並返回這兩個變數的和給客戶端。

這是伺服器端的示例程式碼:

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MAXLINE 1024
#define LISTENQ 5

void str_echo(int connfd);
void sig_chld(int sign);
void err_sys(char *str);

int main(void)
{
	int listenfd, connfd;
	pid_t childpid;
	socklen_t clilen;
	struct sockaddr_in servaddr, cliaddr;

	if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		err_sys("socket error");

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(4321); //4321埠是自己填寫的臨時埠,只要和客戶端上填寫的伺服器埠是該臨時埠就可以了
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
		err_sys("bind error");
	if(listen(listenfd, LISTENQ) < 0)
		err_sys("listen error");

	signal(SIGCHLD, sig_chld);

	for( ; ; )
	{
		clilen = sizeof(cliaddr);
		if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0)
		{
			//if(errno == EINTR)	//如果在Linux下的vim編寫的話可以把這三行註釋到的程式碼給加上去
			//	continue;
			//else
				err_sys("accept error");
		}

		if((childpid = fork()) == 0)
		{
			printf("I am in child %d\n", getpid());
			close(listenfd);
			str_echo(connfd);
			close(connfd);
			exit(0);
		}
		close(connfd);
	}

	return 0;
}

/*
* the function to handle INT of SIGCHLD
*/
void sig_chld(int sign)
{
	pid_t pid;
	int stat;

	while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
		printf("child %d terminated\n", pid);

	return;
}

/*
* send the sum of two long numbers to client
*/
void str_echo(int sockfd)
{
	long arg1, arg2;
	ssize_t n;
	char line[MAXLINE];

	for( ; ; )
	{
		if((n = read(sockfd, line, MAXLINE)) == 0)
			return;
		if(sscanf(line, "%ld%ld", &arg1, &arg2) == 2)
			snprintf(line, sizeof(line), "%ld\n", arg1 + arg2);
		else
			snprintf(line, sizeof(line), "input error");
		n = strlen(line);
		if(write(sockfd, line, n) != n)
			err_sys("write error");
	}
}

void err_sys(char *str)
{
    perror(str);
    exit(1);
}

以下是客戶端的示例程式碼
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#define MAXLINE 1024

void str_cli(int sockfd);
void err_sys(char *str);

int main(int argc, char *argv[])
{
	int sockfd;
	struct sockaddr_in servaddr;

	if(argc != 2)
	{
		printf("usage: ./a.out <ip>\n");
		exit(1);
	}
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		err_sys("socket error");

	printf("sockfd is ok\n");
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(4321);
	if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)
		err_sys("inet_pton error");

	if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
		err_sys("connect error");

	printf("connect is ok\n");
	str_cli(sockfd);

	close(sockfd);

	return 0;
}

/*
* send two long num to server host
*/
void str_cli(int sockfd)
{
	char sendline[MAXLINE], recvline[MAXLINE];

	while(fgets(sendline, MAXLINE, stdin) != NULL)
	{
		if(write(sockfd, sendline, strlen(sendline)) < 0)
            err_sys("write error");
		if(read(sockfd, recvline, MAXLINE) == 0)
			err_sys("read error");
		fputs(recvline, stdout);
		bzero(recvline, strlen(recvline));
	}
}

void err_sys(char *str)
{
    perror(str);
    exit(1);
}

基本套接字函式講解

socket函式

#include <sys/socket.h>
int socket(int family, int type, int protocol);  //成功返回非負描述符,出錯返回-1
          socket函式指定期望的通訊協議型別(使用IPv4的TCP、使用IPv6的UDP、Unix域位元組流協議)和套接字字型別(位元組流、資料報或原始套接字)

----socket函式的family常值 ---------------

family             說明                         
AF_INET       IPv4協議             
AF_INET6     IPv4協議            
AF_LOCAL   Unix協議域      
AF_ROUTE   路由套接字     
AF_KEY         祕鑰套接字         
----------------------------------------------------

----socket函式的type常值 ------------------

SOCK_STREAM           位元組流套接字

SOCK_DGRAM            資料報套接字

SOCK_SEQPACKET  有序分組套接字

SOCK_RAW                 原始套接字

----------------------------------------------------

----socket函式的protocal常值 ------------

IPPROTO_CP        TCP傳輸協議

IPPROTO_UDP     UDP傳輸協議

IPPROTO_SCTP   SCTP傳輸協議

----------------------------------------------------

connect函式

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);  //成功返回0,出錯為-1
        TCP客戶用connect函式來建立一個與TCP伺服器連線,sockfd是由socket函式返回的套接字描述符,第二個、第三個引數分別是指向一個套接字地址結構的指標和該結構的大小,套接字結構必須含有伺服器的IP地址和埠號。如果connect失敗後,就必須close當前的套接字描述符並重新呼叫socket。
bind函式
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);  //成功返回0,出錯-1

        bind函式把一個本地協議地址賦予一個套接字,它只是把一個協議地址賦予一個套接字,至於協議地址的含義則取決於協議本身。第二個引數指向協議地址結構的指標,第三個引數是協議地址的長度,對於TCP,呼叫bind函式可以指定一個埠號,或指定一個IP地址,或兩者都指定,也可以兩者都不指定。

listen函式

#include <sys/socket.h>
int listen(int sockfd, int backlog);  //成功返回0,出錯-1
        socket建立一個套接字時,它被假設為一個主動套接字,也就是說,它是一個將呼叫connect發起連線的一個客戶套接字。listen函式把一個未連線的套接字轉換為一個被動套接字,指示核心應接受指向該套接字的連線請求,呼叫listen函式將導致套接字從CLOSEE狀態轉換到LISTEN狀態。第二個引數規定了核心應為相應套接字排隊的最大連線個數。
(1)、未完成連線佇列:每一個這樣的SYN分節對應其中一項:已由某個客戶發出併到達伺服器,而伺服器正在等待完成相應的TCP三路握手過程。這些套接字處於SYN_RCVD狀態。

(2)、已完成連線佇列:每個完成TCP三路握手過程的客戶對應其中一項,這些套接字處於ESTABLISHED狀態。

------------------------------------------------------------------------------------------------------------------------------------------------------

TCP監聽套接字維護的兩個佇列



accept函式

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);  //成功返回非負描述符,出錯返回-1
       如果accept成功,那麼其返回值是由核心自動生成的一個全新套接字,代表與返回客戶的TCP連線,函式的第一個引數為監聽套接字,返回值為已連線套接字。

------------------------------------------------------------------------------------------------------------------------------------------------------

伺服器、客戶端程式流程圖


------------------------------------------------------------------------------------------------------------------------------------------------------

TCP狀態轉換圖


------------------------------------------------------------------------------------------------------------------------------------------------------

參考資料:

1、《UNIX網路程式設計 卷1 套接字聯網API》 第四章-基本套接字程式設計

2、《計算機網路 - 謝希仁》