1. 程式人生 > >linux下socket程式設計 select實現非阻塞模式多臺客戶端與伺服器通訊

linux下socket程式設計 select實現非阻塞模式多臺客戶端與伺服器通訊

select函式原型如下:

int select (int maxfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select系統呼叫是用來讓我們的程式監視多個檔案控制代碼(socket 控制代碼)的狀態變化的。程式會停在select這裡等待,直到被監視的檔案控制代碼有一個或多個發生了狀態改變

有一片博文寫得非常詳細易理解http://blog.csdn.net/lingfengtengfei/article/details/12392449。推薦大家看看,這裡就不說了。

主要貼程式碼,參考的也是別人的程式碼,但是發現有BUG,努力修正後實現多臺客戶段與一臺伺服器通訊:在非阻塞模式下,伺服器和客戶端可以自由發訊息,不必等待回答,目前伺服器發的訊息,所有客戶端都會收到此訊息。讀者可以自己改一下,讓伺服器與指定的客戶端通訊(可以通過鍵盤輸入要通訊的客戶端編號來控制,或者用棧或佇列來儲存客戶端編號,伺服器在分別傳送訊息):

伺服器端程式碼:

#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#define BACKLOG 5     //完成三次握手但沒有accept的佇列的長度
#define CONCURRENT_MAX 8   //應用層同時可以處理的連線
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
int main(int argc, const char * argv[])
{
    char input_msg[BUFFER_SIZE];
    char recv_msg[BUFFER_SIZE];
    //本地地址
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    bzero(&(server_addr.sin_zero), 8);
    //建立socket
    int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(server_sock_fd == -1)
    {
    	perror("socket error");
    	return 1;
    }
    //繫結socket
    int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if(bind_result == -1)
    {
    	perror("bind error");
    	return 1;
    }
    //listen
    if(listen(server_sock_fd, BACKLOG) == -1)
    {
    	perror("listen error");
    	return 1;
    }
    //fd_set
    fd_set server_fd_set;
    int max_fd = -1;
    struct timeval tv;  //超時時間設定
    while(1)
    {
    	tv.tv_sec = 20;
    	tv.tv_usec = 0;
    	FD_ZERO(&server_fd_set);
    	FD_SET(STDIN_FILENO, &server_fd_set);
    	if(max_fd <STDIN_FILENO)
    	{
    		max_fd = STDIN_FILENO;
    	}
        //printf("STDIN_FILENO=%d\n", STDIN_FILENO);
	//伺服器端socket
        FD_SET(server_sock_fd, &server_fd_set);
       // printf("server_sock_fd=%d\n", server_sock_fd);
        if(max_fd < server_sock_fd)
        {
        	max_fd = server_sock_fd;
        }
	//客戶端連線
        for(int i =0; i < CONCURRENT_MAX; i++)
        {
        	//printf("client_fds[%d]=%d\n", i, client_fds[i]);
        	if(client_fds[i] != 0)
        	{
        		FD_SET(client_fds[i], &server_fd_set);
        		if(max_fd < client_fds[i])
        		{
        			max_fd = client_fds[i];
        		}
        	}
        }
        int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);
        if(ret < 0)
        {
        	perror("select 出錯\n");
        	continue;
        }
        else if(ret == 0)
        {
        	printf("select 超時\n");
        	continue;
        }
        else
        {
        	//ret 為未狀態發生變化的檔案描述符的個數
        	if(FD_ISSET(STDIN_FILENO, &server_fd_set))
        	{
        		printf("傳送訊息:\n");
        		bzero(input_msg, BUFFER_SIZE);
        		fgets(input_msg, BUFFER_SIZE, stdin);
        		//輸入“.quit"則退出伺服器
        		if(strcmp(input_msg, QUIT_CMD) == 0)
        		{
        			exit(0);
        		}
        		for(int i = 0; i < CONCURRENT_MAX; i++)
        		{
        			if(client_fds[i] != 0)
        			{
        				printf("client_fds[%d]=%d\n", i, client_fds[i]);
        				send(client_fds[i], input_msg, BUFFER_SIZE, 0);
        			}
        		}
        	}
        	if(FD_ISSET(server_sock_fd, &server_fd_set))
        	{
        		//有新的連線請求
        		struct sockaddr_in client_address;
        		socklen_t address_len;
        		int client_sock_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
        		printf("new connection client_sock_fd = %d\n", client_sock_fd);
        		if(client_sock_fd > 0)
        		{
        			int index = -1;
        			for(int i = 0; i < CONCURRENT_MAX; i++)
        			{
        				if(client_fds[i] == 0)
        				{
        					index = i;
        					client_fds[i] = client_sock_fd;
        					break;
        				}
        			}
        			if(index >= 0)
        			{
        				printf("新客戶端(%d)加入成功 %s:%d\n", index, inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
        			}
        			else
        			{
        				bzero(input_msg, BUFFER_SIZE);
        				strcpy(input_msg, "伺服器加入的客戶端數達到最大值,無法加入!\n");
        				send(client_sock_fd, input_msg, BUFFER_SIZE, 0);
        				printf("客戶端連線數達到最大值,新客戶端加入失敗 %s:%d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
        			}
        		}
        	}
        	for(int i =0; i < CONCURRENT_MAX; i++)
        	{
        		if(client_fds[i] !=0)
        		{
        			if(FD_ISSET(client_fds[i], &server_fd_set))
        			{
        				//處理某個客戶端過來的訊息
        				bzero(recv_msg, BUFFER_SIZE);
        				long byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0);
        				if (byte_num > 0)
        				{
        					if(byte_num > BUFFER_SIZE)
        					{
        						byte_num = BUFFER_SIZE;
        					}
        					recv_msg[byte_num] = '\0';
        					printf("客戶端(%d):%s\n", i, recv_msg);
        				}
        				else if(byte_num < 0)
        				{
        					printf("從客戶端(%d)接受訊息出錯.\n", i);
        				}
        				else
        				{
        					FD_CLR(client_fds[i], &server_fd_set);
        					client_fds[i] = 0;
        					printf("客戶端(%d)退出了\n", i);
        				}
        			}
        		}
        	}
        }
    }
    return 0;
}
客戶端程式碼:
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#define BUFFER_SIZE 1024

int main(int argc, const char * argv[])
{
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(11332);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    bzero(&(server_addr.sin_zero), 8);

    int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(server_sock_fd == -1)
    {
	perror("socket error");
	return 1;
    }
    char recv_msg[BUFFER_SIZE];
    char input_msg[BUFFER_SIZE];

    if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)
    {
	fd_set client_fd_set;
	struct timeval tv;

	while(1)
	{
		tv.tv_sec = 20;
		tv.tv_usec = 0;
	    FD_ZERO(&client_fd_set);
	    FD_SET(STDIN_FILENO, &client_fd_set);
	    FD_SET(server_sock_fd, &client_fd_set);

	   select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);
		if(FD_ISSET(STDIN_FILENO, &client_fd_set))
		{
		    bzero(input_msg, BUFFER_SIZE);
		    fgets(input_msg, BUFFER_SIZE, stdin);
		    if(send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1)
		    {
		    	perror("傳送訊息出錯!\n");
		    }
		}
		if(FD_ISSET(server_sock_fd, &client_fd_set))
		{
		    bzero(recv_msg, BUFFER_SIZE);
		    long byte_num = recv(server_sock_fd, recv_msg, BUFFER_SIZE, 0);
		    if(byte_num > 0)
		    {
			if(byte_num > BUFFER_SIZE)
			{
			    byte_num = BUFFER_SIZE;
			}
			recv_msg[byte_num] = '\0';
			printf("伺服器:%s\n", recv_msg);
		    }
		    else if(byte_num < 0)
		    {
			printf("接受訊息出錯!\n");
		    }
		    else
		    {
			printf("伺服器端退出!\n");
			exit(0);
		    }
		}
	    }
	//}
    }
    return 0;
}

除錯時發現,select函式每次呼叫後,如果超時,都會把
struct timeval tv 設定為0,這樣再次呼叫select時它會立即返回,根本不會監視socket控制代碼,導致一個超時的死迴圈。
所以每次呼叫select之後都要重新給struct timeval tv 賦值。


相關推薦

linuxsocket程式設計 select實現阻塞模式客戶伺服器通訊

select函式原型如下: int select (int maxfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); select系統呼叫是用來讓我們的程式

Java Socket客戶伺服器通訊

client程式碼: package com.cqut.test4; import java.io.*; import java.net.Socket; import java.net.SocketException; import java.net.U

Linux UDP socket 設定為的阻塞模式阻塞模式區別

UDP socket 設定為的非阻塞模式  ? 1 Len

linuxsocket實現客戶伺服器通訊

學習完《UNIX環境高階程式設計》套接字一章的內容之後,自己實現了單個客戶端與伺服器的通訊程式,後面想想要是多個客戶端如何與伺服器通訊呢?這就有了這篇文章。 伺服器端程式: #include<stdio.h> #include <stdlib.h&g

五、通過Protobuf整合Netty實現對協議訊息客戶伺服器通訊實戰

目錄 一、Protocol Buffers 是什麼? 二、Protocol Buffers 檔案和訊息詳解 三、專案實戰,直接掌握的protobuf應用。 一、Protocol Buffers 是什麼?         1、官網翻譯

實現PHP伺服器+Android客戶(Retrofit+RxJava)第四天客戶伺服器通訊實現

我的上一篇文章已經介紹了retrofit+rxjava的配置(包括快取),從這一篇開始就開始講我要實現的這個app網路請求部分的構思,也就是要請求那些資料,資料的型別等等。 我要實現的客戶端 看圖: 看了介面基本應該能知道要實現的效果了

2017.8.22 用python實現簡單基於TCP/IP的客戶伺服器

伺服器端 import socket serversocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM) serversocket.bind((

Onvif客戶伺服器通訊時鑑權的自實現

OnvifDigest.h /** SHA1 digest size in octets */ #define SOAP_SMD_SHA1_SIZE (20) /** Size of the random nonce */ #define SOAP_WSSE_NONCELEN (20) #define

Linuxsocket程式設計之UDP簡單實現

本文實現一個簡單的UDP小例子,來說明Linux下socket程式設計之UDP的簡單實現。本文主要包括三個部分:伺服器端的實現,客服端的實現和通訊測試。實現的功能:客服端傳送一條訊息給伺服器端,伺服器

linuxsocket程式設計實現一個伺服器連線客戶

使用socekt通訊一般步驟     1)伺服器端:socker()建立套接字,繫結(bind)並

scala通過akka的actor實現socket http server(NIO阻塞模式)

1首先是sbt需要匯入的依賴 name := "HttpServer" version := "1.0" scalaVersion := "2.11.8" libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-act

socket程式設計select實現併發處理

//伺服器客戶端均已修改 //伺服器 #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <

golang中select實現阻塞及超時控制

// select.go package main import ( "fmt" "time" //"time" ) func main() { //宣告一個channel ch := make(chan int) //宣告一個匿名函式,傳入一個引數整型

linuxsocket程式設計基礎示例

本文主要用於記錄(因為有道雲容易丟失資料),程式碼並不規範,所有的內容都解除安裝main()函式裡面了,主要目的是為了方便自己理清流程。 服務端的程式碼: #include<unistd.h> #include<sys/types.h> #incl

linuxsocket程式設計“Broken pipe”錯誤

工作需要,對接伺服器的時候,客戶端傳送資料報錯“Broken pipe” 原因是對一個已關閉的套接字write兩次 細節講解參考:https://www.cnblogs.com/jingzhishen/p/3453727.html   demo 客戶端程式碼: #i

Linux Socket程式設計基礎

作者: 東北大學秦皇島分校軟體中心技術研發部 敬茂華 1、 引言Linux的興起可以說是Internet創造的一個奇蹟。Linux作為一個完全開放其原始碼的免費的自由軟體,相容了各種UNIX標準(

linuxsocket程式設計和epoll的使用

這兩天在學Linux下的網路程式設計,於是便看了些關於socket和epoll的資料。 首先介紹socket,socket程式設計我之前也接觸過,不過是在windows下接觸的。和windows不同的是,windows下關於socket程式設計,

Linuxsocket程式設計執行緒TCP伺服器

程式碼如下: thread_server.c #include<string.h> #include<stdlib.h> #include<stdio.h> #include<sys/types.h> #i

C++伺服器(一):瞭解Linuxsocket程式設計

最近想要用C++寫個socket的伺服器,用於日常的專案開發。 不過,我是新手,那就慢慢地學習一下吧。 Server #include<iostream> using namespace std; //head files of

Linuxsocket程式設計程序TCP伺服器

程式碼如下: tcp_server.c #include<string.h> #include<stdlib.h> #include<stdio.h> #include<sys/types.h> #includ