国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux socket網(wǎng)絡(luò)編程實(shí)戰(zhàn)(tcp)實(shí)現(xiàn)雙方聊天

這篇具有很好參考價(jià)值的文章主要介紹了Linux socket網(wǎng)絡(luò)編程實(shí)戰(zhàn)(tcp)實(shí)現(xiàn)雙方聊天。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

在上節(jié)已經(jīng)系統(tǒng)介紹了大致的流程和相關(guān)的API,這節(jié)就開始寫代碼!

回顧上節(jié)的流程:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

創(chuàng)建一個(gè)NET文件夾 來(lái)存放網(wǎng)絡(luò)編程相關(guān)的代碼:

tcp服務(wù)端代碼初步實(shí)現(xiàn)--上

這部分先實(shí)現(xiàn)服務(wù)器的連接部分的代碼并進(jìn)行驗(yàn)證

server1.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>



int main()
{
	int sockfd;
	int conn_sockfd;
	int ret;

	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	memset(&my_addr,0,sizeof(struct sockaddr_in));
	memset(&client_addr,0,sizeof(struct sockaddr_in));

	//socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return 1;
	}else{
		printf("socket success, sockfd = %d\n",sockfd);
	}

	//bind
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(8888);//host to net (2 bytes)
	inet_aton("192.168.20.137",&my_addr.sin_addr); //char* format -> net format

	ret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in));
	if(ret == -1){
                perror("bind");
                return 1;
        }else{
               printf("bind success\n");
        }

	//listen
	ret = listen(sockfd,10);
	if(ret == -1){
                perror("listen");
                return 1;
        }else{
               printf("listening...\n");
        }

	//accept
	int len = sizeof(struct sockaddr_in);
	conn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);
	if(conn_sockfd == -1){
                perror("accept");
                return 1;
        }else{
               printf("accept success, client IP = %s\n",inet_ntoa(client_addr.sin_addr));//將網(wǎng)絡(luò)格式的IP地址再轉(zhuǎn)回字符串

        }

	//read
	

	//write
	
	//read
	
	//close


	return 0;
}

代碼驗(yàn)證:

先編譯并運(yùn)行這部分代碼:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

可見,此時(shí)沒(méi)有客戶端進(jìn)行連接,程序會(huì)阻塞在監(jiān)聽的階段

此時(shí)打開windows的cmd(windows系統(tǒng)和linux虛擬機(jī)的系統(tǒng)可以看作兩臺(tái)不同的終端

telnet指令使用的也是TCP協(xié)議

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

執(zhí)行這條命令后,windows的cmd變成了這樣:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

再反觀linux虛擬機(jī):

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

使用windows的ipconfig可以驗(yàn)證IP地址?:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

所以,連接部分的代碼已經(jīng)成功,只是因?yàn)闆](méi)有接下來(lái)的數(shù)據(jù)傳輸所以退出了。?

tcp服務(wù)端代碼初步實(shí)現(xiàn)--下

這部分實(shí)現(xiàn)服務(wù)器的連接成功后的讀寫并驗(yàn)證

server1.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>



int main()
{
	int sockfd;
	int conn_sockfd;
	int ret;
	int n_read;
	int n_write;
	char readbuf[1024];

	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	memset(&my_addr,0,sizeof(struct sockaddr_in));
	memset(&client_addr,0,sizeof(struct sockaddr_in));

	//socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return 1;
	}else{
		printf("socket success, sockfd = %d\n",sockfd);
	}

	//bind
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(8888);//host to net (2 bytes)
	inet_aton("192.168.20.137",&my_addr.sin_addr); //char* format -> net format

	ret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in));
	if(ret == -1){
                perror("bind");
                return 1;
        }else{
               printf("bind success\n");
        }

	//listen
	ret = listen(sockfd,10);
	if(ret == -1){
                perror("listen");
                return 1;
        }else{
               printf("listening...\n");
        }

	//accept
	int len = sizeof(struct sockaddr_in);
	conn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);
	if(conn_sockfd == -1){
                perror("accept");
                return 1;
        }else{
               printf("accept success, client IP = %s\n",inet_ntoa(client_addr.sin_addr));

        }

	//read
	n_read = read(conn_sockfd,&readbuf,1024);
	if(n_read == -1){
		perror("read");
		return 1;
	}else{
		printf("%d bytes has been read, context = %s\n",n_read,readbuf);
	}

	//write
	char *msg = "this is server, I have received your msg\n";
	n_write = write(conn_sockfd,msg,strlen(msg));
	if(n_write == -1){
                perror("write");
                return 1;
        }else{
                printf("%d bytes has been written\n",n_write);
        }
	
	//read
	
	
	//close


	return 0;
}

代碼驗(yàn)證:

這部分如果用windows的telnet,打一個(gè)符號(hào)就會(huì)直接結(jié)束,所以用Linux另開一個(gè)cmd使用telnet來(lái)模擬服務(wù)器和客戶端的對(duì)話:

還是先運(yùn)行代碼:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

然后運(yùn)行telnet:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

反觀服務(wù)端:

?linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

在連接成功后客戶端輸入一句話然后回車:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

客戶端顯示:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

服務(wù)器顯示:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

可見,數(shù)據(jù)的交互基本沒(méi)什么問(wèn)題。

至此,構(gòu)建了一個(gè)大致的服務(wù)器代碼框架,可以開始著手編寫客戶端的代碼了。

客戶端代碼初步實(shí)現(xiàn)

client.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>



int main()
{
	int sockfd;
	int ret;
	int n_read;
	int n_write;
	char readbuf[1024];

	struct sockaddr_in server_addr;
	memset(&server_addr,0,sizeof(struct sockaddr_in));

	//socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return 1;
	}else{
		printf("socket success, sockfd = %d\n",sockfd);
	}

	//connect
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(8888);//host to net (2 bytes)
	inet_aton("192.168.20.137",&server_addr.sin_addr); 
	ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));
	if(ret == -1){
		perror("connect");
		return 1;
	}else{
		printf("connect success!\n");
	}

	//write
        char *msg = "client: hello\n";
        n_write = write(sockfd,msg,strlen(msg));
        if(n_write == -1){
                perror("write");
                return 1;
        }else{
                printf("%d bytes has been written\n",n_write);
        }

	//read
	n_read = read(sockfd,&readbuf,1024);
	if(n_read == -1){
		perror("read");
		return 1;
	}else{
		printf("%d bytes has been read, context = %s\n",n_read,readbuf);
	}

	
	
	//close


	return 0;
}

代碼驗(yàn)證:

先編譯并允許服務(wù)端:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

再編譯并運(yùn)行客戶端:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

?回看服務(wù)端:

?linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

客戶端 & 服務(wù)端 代碼的最終實(shí)現(xiàn)

之前,已經(jīng)大致編寫出了客戶端和服務(wù)端的代碼,但是和本節(jié)開頭的框圖對(duì)比,發(fā)現(xiàn)還差一些,首先,數(shù)據(jù)的發(fā)送和讀取應(yīng)該持續(xù)進(jìn)行,直到收到結(jié)束信號(hào),另外在最后要編寫關(guān)閉套接字,關(guān)閉連接的代碼,且服務(wù)器應(yīng)該可以接受多個(gè)客戶端的數(shù)據(jù),還有一些細(xì)節(jié)的優(yōu)化

回顧之前關(guān)于fork函數(shù)的一節(jié):進(jìn)程的創(chuàng)建_mjmmm的博客-CSDN博客

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

?所以此處可以使用fork函數(shù)!

小知識(shí)點(diǎn):自動(dòng)對(duì)齊(gg=G)

(參考:linux代碼對(duì)齊快捷鍵和man幫助文檔的使用總結(jié)_陌上花開緩緩歸以的博客-CSDN博客)

在命令模式下(即非“插入”等編輯模式),先輸入gg,這時(shí)候光標(biāo)會(huì)移動(dòng)到第一行第一個(gè)字符,然后按 “=” 號(hào)之后切換成大寫,再按一下G,這時(shí)候光標(biāo)會(huì)移到最后一行的第一個(gè)字符,這時(shí)候就可以看到代碼被排得整整齊齊了!
“gg"將光標(biāo)移動(dòng)到代碼首部,”="表示對(duì)齊指令,"G"表示代碼尾部,所以執(zhí)行"gg=G"后,該文件的所有代碼都將對(duì)齊!

實(shí)現(xiàn)思路

服務(wù)端:

在listen之后,進(jìn)入一個(gè)while(1)循環(huán),并調(diào)用accept阻塞,一旦連接到一個(gè)客戶端,就fork一個(gè)子進(jìn)程來(lái)處理數(shù)據(jù),父進(jìn)程則繼續(xù)通過(guò)while(1)繼續(xù)調(diào)用accept阻塞等待下一個(gè)客戶端連接。

而子進(jìn)程中再次調(diào)用fork,父進(jìn)程寫一個(gè)while(1)不斷的寫數(shù)據(jù),子進(jìn)程寫一個(gè)while(1)不斷的讀數(shù)據(jù)。

客戶端:

在connect之后進(jìn)行fork,父進(jìn)程寫一個(gè)while(1)不斷的寫數(shù)據(jù),子進(jìn)程寫一個(gè)while(1)不斷的讀數(shù)據(jù)。

如何退出:

我希望的退出方式是客戶端輸入“quit”就會(huì)退出,但是不管是客戶端還是服務(wù)端,為了讀和寫不會(huì)相互阻塞,都在不同的進(jìn)程中的while(1)里,當(dāng)客戶端輸入“quit”之后,只有客戶端的寫端和服務(wù)器的讀端知道,客戶端的讀端和服務(wù)器的寫端并不知情,所以需要使用進(jìn)程間的通訊,此處我使用了FIFO


在客戶端中創(chuàng)建FIFO并在不斷寫數(shù)據(jù)的父進(jìn)程不斷檢測(cè)是否輸入了“quit”,如果是就只寫打開fifo,并阻塞等待....一旦等待到了有進(jìn)程只讀打開FIFO,就會(huì)往FIFO寫入“quit”,然后關(guān)閉FIFO;關(guān)閉套接字;收集子進(jìn)程退出狀態(tài);然后退出循環(huán);正常退出。 同時(shí)在不斷讀數(shù)據(jù)的子進(jìn)程不斷非阻塞的只讀打開fifo,并每次都將光標(biāo)移到最開頭,一旦從FIFO讀取到了“quit”,就exit。


但是,不斷讀數(shù)據(jù)的子進(jìn)程會(huì)阻塞讀取服務(wù)器傳來(lái)(寫入)的數(shù)據(jù),這就導(dǎo)致,當(dāng)客戶端輸入“quit”之后無(wú)法立刻退出,而是要等到服務(wù)器再發(fā)來(lái)消息,才能進(jìn)行下一輪的FIFO讀取,才能使得子進(jìn)程收到父進(jìn)程通過(guò)FIFO發(fā)來(lái)的“quit”并退出。解決辦法就是:在服務(wù)器端中一旦檢測(cè)到客戶端發(fā)來(lái)的消息是quit之后,就立刻給客戶端發(fā)送一句話


此時(shí),對(duì)于客戶端來(lái)說(shuō)輸入了“quit”之后會(huì)立刻退出,但是服務(wù)端只有讀端的while可以退出,寫端的while無(wú)法退出,此時(shí)就有一個(gè)疑問(wèn)“我在讀端關(guān)閉了客戶端套接字,照理說(shuō)寫端應(yīng)該往這個(gè)套接字里寫會(huì)報(bào)錯(cuò),我直接在報(bào)錯(cuò)處理函數(shù)里退出寫端不就行了”,但其實(shí)這是行不通的,因?yàn)槲募枋龇淖饔糜蚰J(rèn)情況下只在進(jìn)程內(nèi)有效,而無(wú)法在進(jìn)程之間進(jìn)行傳遞。所以還是需要使用FIFO,且注意,FIFO是進(jìn)程與進(jìn)程間的,客戶端和服務(wù)端本質(zhì)也屬于兩個(gè)進(jìn)程,所以服務(wù)端如果要使用FIFO應(yīng)該在mkfifo函數(shù)中對(duì)于FIFO的名字修改,不要和服務(wù)端的FIFO重名


在服務(wù)器端創(chuàng)建另一個(gè)FIFO并在不斷讀數(shù)據(jù)的子進(jìn)程不斷判斷是否從客戶端收到了“quit”,如果收到了就立刻回復(fù)(寫)一個(gè)“Bye”(也就是為了讓客戶端能立刻退出);只寫打開FIFO,并阻塞等待....一旦等待到了有進(jìn)程只讀打開FIFO,就會(huì)往FIFO寫入“quit”,然后exit。?同時(shí)在不斷寫數(shù)據(jù)的父進(jìn)程不斷非阻塞的只讀打開fifo,并每次都將光標(biāo)移到最開頭,一旦從FIFO讀取到了“quit”,就關(guān)閉FIFO;關(guān)閉客戶端的套接字;收集子進(jìn)程退出狀態(tài);然后退出循環(huán);執(zhí)行fork之后的exit。

程序框圖:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

server_final.c:

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


int main(int argc, char **argv)
{
	int conn_num = 0;
	int flag = 0;
	int sockfd;
	int conn_sockfd;
	int ret;
	int n_read;
	int n_write;
	int len = sizeof(struct sockaddr_in);
	char readbuf[128];
	char msg[128];

	int fd; //fifo
	char fifo_readbuf[20] = {0};
	char *fifo_msg = "quit";

	pid_t fork_return;
	pid_t fork_return_1;

	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	memset(&my_addr,0,sizeof(struct sockaddr_in));
	memset(&client_addr,0,sizeof(struct sockaddr_in));

	if(argc != 3){
		printf("param error!\n");
		return 1;
	}

	//socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return 1;
	}else{
		printf("socket success, sockfd = %d\n",sockfd);
	}

	//bind
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));//host to net (2 bytes)
	inet_aton(argv[1],&my_addr.sin_addr); //char* format -> net format

	ret = bind(sockfd, (struct sockaddr *)&my_addr, len);
	if(ret == -1){
		perror("bind");
		return 1;
	}else{
		printf("bind success\n");
	}

	//listen
	ret = listen(sockfd,10);
	if(ret == -1){
		perror("listen");
		return 1;
	}else{
		printf("listening...\n");
	}

	//fifo
	if(mkfifo("./fifo1",S_IRWXU) == -1 && errno != EEXIST)
	{
		perror("fifo");
	}


	while(1){
		//accept
		conn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);
		if(conn_sockfd == -1){
			perror("accept");
			return 1;
		}else{
			conn_num++;
			if(conn_num > 1){
				printf("there are more then one client, msg may not be sent accuratly!\n");
			}
			printf("accept success, no.%d client IP = %s\n",conn_num,inet_ntoa(client_addr.sin_addr));

		}

		fork_return = fork();

		if(fork_return > 0){//father keeps waiting for new request
			//wait(NULL); //cant wait,will block	
		}else if(fork_return < 0){
			perror("fork");
			return 1;
		}else{//son deals with request
			fork_return_1 = fork();
			if(fork_return_1 > 0){//father keeps writing msg
				while(1){
					fd = open("./fifo1",O_RDONLY|O_NONBLOCK);
					lseek(fd, 0, SEEK_SET);
					read(fd,&fifo_readbuf,20);
					//printf("read from fifo:%s\n",fifo_readbuf);
					if(fifo_readbuf[0]=='q' && fifo_readbuf[1]=='u' && fifo_readbuf[2]=='i' && fifo_readbuf[3]=='t'){
						printf("sorry,the last msg sent fail,client has quit\n");
						close(fd);
						close(conn_sockfd);
						wait(NULL);
						break;
					}


					//write
					memset(&msg,0,sizeof(msg));
					//printf("\ntype msg:");
					scanf("%s",(char *)msg);
					n_write = write(conn_sockfd,&msg,strlen(msg));
					if(n_write == -1){
						perror("write");
						return 1;
					}else{
						printf("%d bytes msg sent\n",n_write);

					}
				}

			}else if(fork_return_1 < 0){
				perror("fork");
				return 1;
			}else{//son keeps reading msg
				while(1){
					//read
					memset(&readbuf,0,sizeof(readbuf));
					n_read = read(conn_sockfd,&readbuf,128);
					if(readbuf[0]=='q' && readbuf[1]=='u' && readbuf[2]=='i' && readbuf[3]=='t'){
						printf("client quit\n");
						conn_num--;
						printf("%d client remain\n",conn_num);
						write(conn_sockfd,"BYE",3);
						fd = open("./fifo1",O_WRONLY);
						write(fd,fifo_msg,strlen(fifo_msg));
						exit(1);
					}
					if(n_read == -1){
						perror("read");
						return 1;
					}else{
						printf("\nclient: %s\n",readbuf);
					}

				}
			}
			exit(2);
		}

	}


	return 0;
}

client_final.c:

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


int main(int argc, char **argv)
{
	int sockfd;
	int ret;
	int n_read;
	int n_write;
	char readbuf[128];
	char msg[128];

	int fd; //fifo
	char fifo_readbuf[20] = {0};
	char *fifo_msg = "quit";

	pid_t fork_return;

	if(argc != 3){
		printf("param error!\n");
		return 1;
	}


	struct sockaddr_in server_addr;
	memset(&server_addr,0,sizeof(struct sockaddr_in));

	//socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return 1;
	}else{
		printf("socket success, sockfd = %d\n",sockfd);
	}

	//connect
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));//host to net (2 bytes)
	inet_aton(argv[1],&server_addr.sin_addr); 
	ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));
	if(ret == -1){
		perror("connect");
		return 1;
	}else{
		printf("connect success!\n");
	}

	//fifo
	if(mkfifo("./fifo",S_IRWXU) == -1 && errno != EEXIST)
	{
		perror("fifo");
	}

	//fork
	fork_return = fork();

	if(fork_return > 0){//father keeps writing msg
		while(1){
			//write
			memset(&msg,0,sizeof(msg));
			//printf("\ntype msg:");
			scanf("%s",(char *)msg);
			n_write = write(sockfd,&msg,strlen(msg));
			if(msg[0]=='q' && msg[1]=='u' && msg[2]=='i' && msg[3]=='t'){
				printf("quit detected!\n");
				fd = open("./fifo",O_WRONLY);
				write(fd,fifo_msg,strlen(fifo_msg));
				close(fd);
				close(sockfd);
				wait(NULL);
				break;
			}
			if(n_write == -1){
				perror("write");
				return 1;
			}else{
				printf("%d bytes msg sent\n",n_write);
			}
		}
	}else if(fork_return < 0){
		perror("fork");
		return 1;
	}else{//son keeps reading 
		while(1){
			fd = open("./fifo",O_RDONLY|O_NONBLOCK);
			lseek(fd, 0, SEEK_SET);
			read(fd,&fifo_readbuf,20);
			//printf("read from fifo:%s\n",fifo_readbuf);
			if(fifo_readbuf[0]=='q' && fifo_readbuf[1]=='u' && fifo_readbuf[2]=='i' && fifo_readbuf[3]=='t'){
				exit(1);
			}

			//read
			memset(&readbuf,0,sizeof(readbuf));
			n_read = read(sockfd,&readbuf,128);
			if(n_read == -1){
				perror("read");
				return 1;
			}else{
				printf("\nserver: %s\n",readbuf);
			}
		}

	}


	return 0;
}

實(shí)現(xiàn)效果:

編譯并運(yùn)行server端:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

編譯并運(yùn)行client端:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

此時(shí)回看server端:

?linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

此時(shí),就可以實(shí)現(xiàn)雙方聊天的功能了:(服務(wù)器左,客戶端右)

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

為什么不打成hows it?going而是hows/it/going,原因是scanf函數(shù),如果占位符是“%s”即字符串時(shí),空格和換行鍵都會(huì)被視為結(jié)束符號(hào)!

(詳見:scanf與空格_scanf讀空格_CSU迦葉的博客-CSDN博客

所以如果輸入hows it going的話:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

解決方法是使用fgets函數(shù)

(詳見:如何讀取帶空格的字符串?

直到客戶端打出“quit” :

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

客戶端會(huì)立刻退出,服務(wù)器此時(shí)只有讀端知道客戶端退出了

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

必須再打一句話讓服務(wù)器寫端刷新,這樣才能從FIFO讀取到信息,讓寫端也知道客戶端退出了:

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

此時(shí),服務(wù)器的讀寫端也全部退出,再次進(jìn)入阻塞狀態(tài),等待新連接...

下面模擬如果同時(shí)有兩個(gè)或以上連接的時(shí)候:(左側(cè)一個(gè)服務(wù)器,右側(cè)兩個(gè)客戶端)?

linux網(wǎng)絡(luò)編程 聊天,linux,網(wǎng)絡(luò),運(yùn)維,系統(tǒng)編程,C語(yǔ)言,tcp/ip

?我代碼執(zhí)行的順序是:連接第一個(gè)客戶端(右上)--> 服務(wù)器發(fā)送“hi”?-->?連接第二個(gè)客戶端(右下)--> 服務(wù)器發(fā)送“hihi”?--> 服務(wù)器發(fā)送“hihihi”?--> 服務(wù)器發(fā)送“hihihihi”?

可見,連接第一個(gè)客戶端的時(shí)候,服務(wù)器發(fā)送hi準(zhǔn)確的到了右上的客戶端1,但是當(dāng)連接第二個(gè)客戶端了之后,服務(wù)器分別發(fā)送了“hihi”,“hihihi” ,“hihihihi” ,從服務(wù)端看沒(méi)有任何區(qū)別,但實(shí)際情況下“hihi”和“hihihihi”到了客戶端1,“hihihi”到了客戶端2,明顯出現(xiàn)了混亂,所以我在代碼中設(shè)置了提醒,檢測(cè)到大于1個(gè)客戶端接入時(shí)會(huì)提醒。

問(wèn)題探討 和 一些思路過(guò)程

  • 如剛剛所說(shuō),服務(wù)器用了兩次fork,相當(dāng)于有3個(gè)獨(dú)立的進(jìn)程,所以我的conn_num變量的設(shè)置實(shí)際上是相當(dāng)殘疾的,因?yàn)槲野裞onn_num-- 放在了一個(gè)子進(jìn)程里,而fork之后的變量空間都是獨(dú)立的,所以我的conn_num變量只要有客戶端推出就不準(zhǔn)了,應(yīng)該也使用進(jìn)程間通信來(lái)通知,但是鑒于我的代碼實(shí)現(xiàn)目的本來(lái)就是雙人聊天,所以多客戶端的連接部分就沒(méi)有深入修改了
  • 在我目前的代碼邏輯中,我本來(lái)覺得只設(shè)置了一個(gè)conn_sockfd變量來(lái)存放客戶端的套接字不合理,因?yàn)楫?dāng)多個(gè)客戶端接入的時(shí)候,套接字可能會(huì)被覆蓋,導(dǎo)致讀寫異常,但是其實(shí)一個(gè)就夠了,原因也是因?yàn)閒ork之后變量空間也會(huì)被復(fù)制,根據(jù)我的代碼邏輯,每出現(xiàn)一個(gè)新連接,就會(huì)fork一個(gè)子進(jìn)程來(lái)處理這個(gè)連接的讀寫,如果有多個(gè)客戶端,就會(huì)有多個(gè)子進(jìn)程,里面的套接字變量名都是conn_sockfd,但其實(shí)值是不一樣的。
  • 并且,服務(wù)端的第一個(gè)fork,父進(jìn)程不能調(diào)用wait,因?yàn)閣ait會(huì)阻塞直到子進(jìn)程退出,但我希望父進(jìn)程不被阻塞而一直while循環(huán)等待新連接,所以第一次fork生成的子進(jìn)程在客戶端退出之后會(huì)變成僵尸進(jìn)程,且每有一個(gè)新的客戶端退出就會(huì)多一個(gè)僵尸進(jìn)程,在當(dāng)前的邏輯下不可避免。

且,當(dāng)多個(gè)客戶端連接服務(wù)器的時(shí)候,服務(wù)器會(huì)針對(duì)每個(gè)客戶端fork一個(gè)子進(jìn)程來(lái)處理,而每一個(gè)子進(jìn)程都會(huì)再fork一個(gè)讀端不停的scanf檢測(cè)輸入。

但是進(jìn)程與進(jìn)程之間是競(jìng)爭(zhēng)的關(guān)系,所以在cmd中看來(lái),光標(biāo)一直在閃沒(méi)有變化,但實(shí)際上,可能上一秒這是客戶端1對(duì)應(yīng)子進(jìn)程的scanf,下一秒就變成了客戶端2對(duì)應(yīng)子進(jìn)程的scanf

這就導(dǎo)致了如果此時(shí)對(duì)著光標(biāo)輸入消息并回車,無(wú)法確定收到消息的是客戶端1還是客戶端2。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-770542.html

  • 解決辦法1:使用線程,實(shí)現(xiàn)真正的多方數(shù)據(jù)收發(fā),但是難度很大,需要更多的新知識(shí)
  • 解決辦法2:舍棄服務(wù)器的scanf功能,改為自動(dòng)回復(fù),這樣可以實(shí)現(xiàn)多個(gè)客戶端對(duì)服務(wù)器的自定義消息發(fā)送,但是服務(wù)器只能回復(fù)預(yù)設(shè)的內(nèi)容
  • 解決辦法3:(也就是現(xiàn)在實(shí)現(xiàn)的效果)舍棄多方發(fā)送,只用一個(gè)客戶端,這樣就可以實(shí)現(xiàn)客戶端和服務(wù)器的自定義消息交互,但是此時(shí)不能增加更多的客戶端

到了這里,關(guān)于Linux socket網(wǎng)絡(luò)編程實(shí)戰(zhàn)(tcp)實(shí)現(xiàn)雙方聊天的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Socket編程接口API并實(shí)現(xiàn)簡(jiǎn)單的TCP網(wǎng)絡(luò)編程

    Socket編程接口API并實(shí)現(xiàn)簡(jiǎn)單的TCP網(wǎng)絡(luò)編程

    #include sys/types.h #include sys/socket.h socket()創(chuàng)建套接字,成功返回套接字的文件描述符,失敗返回-1 domain: 設(shè)置套接字的協(xié)議簇, AF_UNIX AF_INET AF_INET6 type: 設(shè)置套接字的服務(wù)類型 SOCK_STREAM SOCK_DGRAM protocol: 一般設(shè)置為 0,表示使用默認(rèn)協(xié)議 int socket(int domain, int type, int protocol); bind()將

    2024年02月13日
    瀏覽(31)
  • C/C++ Linux Socket網(wǎng)絡(luò)編程 TCP 與 UDP

    C/C++ Linux Socket網(wǎng)絡(luò)編程 TCP 與 UDP

    之前已經(jīng)學(xué)習(xí)了QT的socket編程 和 C/C++在window環(huán)境的socket編程,現(xiàn)在再來(lái)學(xué)習(xí)一波C/C++在Linux環(huán)境下的socket編程,為以后學(xué)習(xí)C++ Linux 服務(wù)器開發(fā)做準(zhǔn)備。 目錄 一、Socket簡(jiǎn)介 二、Socket編程基礎(chǔ) 1. 網(wǎng)絡(luò)字節(jié)序 2. sockaddr數(shù)據(jù)結(jié)構(gòu) 3. IP地址轉(zhuǎn)換函數(shù) 三、TCP編程函數(shù) 1. socket函數(shù) 2.?bi

    2024年02月02日
    瀏覽(25)
  • 「網(wǎng)絡(luò)編程」第二講:網(wǎng)絡(luò)編程socket套接字(三)_ 簡(jiǎn)單TCP網(wǎng)絡(luò)通信程序的實(shí)現(xiàn)

    「網(wǎng)絡(luò)編程」第二講:網(wǎng)絡(luò)編程socket套接字(三)_ 簡(jiǎn)單TCP網(wǎng)絡(luò)通信程序的實(shí)現(xiàn)

    「前言」文章是關(guān)于網(wǎng)絡(luò)編程的socket套接字方面的,上一篇是網(wǎng)絡(luò)編程socket套接字(二),下面開始講解!? 「歸屬專欄」網(wǎng)絡(luò)編程 「主頁(yè)鏈接」個(gè)人主頁(yè) 「筆者」楓葉先生(fy) 「楓葉先生有點(diǎn)文青病」「每篇一句」 I?do?not?know?where?to?go,but?I?have?been?on?the?road. 我不知

    2024年02月11日
    瀏覽(29)
  • Socket網(wǎng)絡(luò)編程(TCP/IP)實(shí)現(xiàn)服務(wù)器/客戶端通信。

    Socket網(wǎng)絡(luò)編程(TCP/IP)實(shí)現(xiàn)服務(wù)器/客戶端通信。

    一.前言 回顧之前進(jìn)程間通信(無(wú)名管道,有名管道,消息隊(duì)列,共享內(nèi)存,信號(hào),信號(hào)量),都是在同一主機(jī)由內(nèi)核來(lái)完成的通信。 那不同主機(jī)間該怎么通信呢? 可以使用Socket編程來(lái)實(shí)現(xiàn)。 Socket編程可以通過(guò)網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)實(shí)現(xiàn)不同主機(jī)之間的通訊。 二.Socket編程的網(wǎng)絡(luò)模型如

    2024年02月08日
    瀏覽(37)
  • python網(wǎng)絡(luò)編程:通過(guò)socket實(shí)現(xiàn)TCP客戶端和服務(wù)端

    python網(wǎng)絡(luò)編程:通過(guò)socket實(shí)現(xiàn)TCP客戶端和服務(wù)端

    目錄 寫在開頭 socket服務(wù)端(基礎(chǔ)) socket客戶端(基礎(chǔ)) 服務(wù)端實(shí)現(xiàn)(可連接多個(gè)客戶端)? 客戶端實(shí)現(xiàn) 數(shù)據(jù)收發(fā)效果 ? 近期可能會(huì)用python實(shí)現(xiàn)一些網(wǎng)絡(luò)安全工具,涉及到許多關(guān)于網(wǎng)絡(luò)的知識(shí),逃不過(guò)的就是最基本的socket。本文將介紹如何通過(guò)python自帶的socket庫(kù)實(shí)現(xiàn)TCP客戶

    2024年03月21日
    瀏覽(27)
  • 【Java】網(wǎng)絡(luò)編程與Socket套接字、UDP編程和TCP編程實(shí)現(xiàn)客戶端和服務(wù)端通信

    【Java】網(wǎng)絡(luò)編程與Socket套接字、UDP編程和TCP編程實(shí)現(xiàn)客戶端和服務(wù)端通信

    為什么需要網(wǎng)絡(luò)編程? 現(xiàn)在網(wǎng)絡(luò)普及程序越來(lái)越高,網(wǎng)絡(luò)上保存著我們?nèi)粘I钪行枰母鞣N資源,使用程序通過(guò)網(wǎng)絡(luò)來(lái)獲取這些資源的過(guò)程就需要網(wǎng)絡(luò)編程來(lái)實(shí)現(xiàn)。 什么是網(wǎng)絡(luò)編程? 網(wǎng)絡(luò)編程,指網(wǎng)絡(luò)上的主機(jī),通過(guò)不同的進(jìn)程以程序的方式實(shí)現(xiàn)網(wǎng)絡(luò)通信(網(wǎng)絡(luò)數(shù)據(jù)傳輸)

    2024年02月17日
    瀏覽(91)
  • Linux下網(wǎng)絡(luò)編程(3)——socket編程實(shí)戰(zhàn),如何構(gòu)建一個(gè)服務(wù)器和客戶端連接

    Linux下網(wǎng)絡(luò)編程(3)——socket編程實(shí)戰(zhàn),如何構(gòu)建一個(gè)服務(wù)器和客戶端連接

    ????????經(jīng)過(guò)前幾篇的介紹,本文我們將進(jìn)行編程實(shí)戰(zhàn),實(shí)現(xiàn)一個(gè)簡(jiǎn)單地服務(wù)器和客戶端應(yīng)用程序。 編寫服務(wù)器程序 ???????? 編寫服務(wù)器應(yīng)用程序的流程如下: ????????①、調(diào)用 socket()函數(shù)打開套接字,得到套接字描述符; ????????②、調(diào)用 bind()函數(shù)將套接字

    2024年02月03日
    瀏覽(30)
  • 「網(wǎng)絡(luò)編程」第二講:socket套接字(四 - 完結(jié))_ Linux任務(wù)管理與守護(hù)進(jìn)程 | TCP協(xié)議通訊流程

    「網(wǎng)絡(luò)編程」第二講:socket套接字(四 - 完結(jié))_ Linux任務(wù)管理與守護(hù)進(jìn)程 | TCP協(xié)議通訊流程

    「前言」文章是關(guān)于網(wǎng)絡(luò)編程的socket套接字方面的,上一篇是網(wǎng)絡(luò)編程socket套接字(三),這篇續(xù)上篇文章的內(nèi)容,下面開始講解!? 「歸屬專欄」網(wǎng)絡(luò)編程 「主頁(yè)鏈接」個(gè)人主頁(yè) 「筆者」楓葉先生(fy) 「楓葉先生有點(diǎn)文青病」「句子分享」 Time?goes?on?and?on,?never?to?an?

    2024年02月10日
    瀏覽(47)
  • Socket實(shí)例,實(shí)現(xiàn)多個(gè)客戶端連接同一個(gè)服務(wù)端代碼&TCP網(wǎng)絡(luò)編程 ServerSocket和Socket實(shí)現(xiàn)多客戶端聊天

    Socket實(shí)例,實(shí)現(xiàn)多個(gè)客戶端連接同一個(gè)服務(wù)端代碼&TCP網(wǎng)絡(luò)編程 ServerSocket和Socket實(shí)現(xiàn)多客戶端聊天

    Java socket(套接字)通常也稱作\\\"套接字\\\",用于描述ip地址和端口,是一個(gè)通信鏈的句柄。應(yīng)用程序通常通過(guò)\\\"套接字\\\"向網(wǎng)絡(luò)發(fā)出請(qǐng)求或者應(yīng)答網(wǎng)絡(luò)請(qǐng)求。 使用socket實(shí)現(xiàn)多個(gè)客戶端和同一客戶端通訊;首先客戶端連接服務(wù)端發(fā)送一條消息,服務(wù)端接收到消息后進(jìn)行處理,完成后再

    2024年02月12日
    瀏覽(89)
  • 【網(wǎng)絡(luò)編程】TCP Socket編程

    【網(wǎng)絡(luò)編程】TCP Socket編程

    流套接字: 使用傳輸層TCP協(xié)議 TCP: 即Transmission Control Protocol(傳輸控制協(xié)議),傳輸層協(xié)議。 TCP的特點(diǎn): 有連接 可靠傳輸 面向字節(jié)流 有接收緩沖區(qū),也有發(fā)送緩沖區(qū) 大小不限 ServerSocket 是 創(chuàng)建TCP服務(wù)端Socket 的API。 注意: ServerSocket 只能用于 服務(wù)器端。 構(gòu)造方法: 方法簽名

    2024年02月07日
    瀏覽(26)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包