`
prettyinsight
  • 浏览: 7573 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

select系统调用以及一个简单的TCP代理程序

 
阅读更多

      假设需要编写一个简单的聊天程序,它要做两件事:第一,从键盘读入想要发给对方的聊天内容,然后把它通过socket发送给对方。第二,从socket接收来自对方的聊天内容,然后把它输出到屏幕上。这两件事不可能依次进行,因为你不可能会忍受一个聊天程序要等到在你发完消息后才能接收消息或者接收消息后才能发消息。为了这两件事能够互不影响的进行,一个解决方法是使用多线程,让两个线程分别去做这两件事。另一个解决方法就是使用select系统调用。

      为了使用select,需要了解在Linux上数据的写入和读取都是通过文件描述符进行的。文件描述符由非负整数表示,例如从键盘读取是通过文件描述符0,输出到屏幕上是通过文件描述符1。并且这些文件描述符都已设为非阻塞模式。

      select的作用是能够同时对多个文件描述符进行监测,筛选出哪些已有数据可以读取,哪些可以写入。

      现在来看看select系统调用的函数原型,它在头文件sys/select.h中定义。

 

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

      需要监测用来读取数据的文件描述符放入readfds集合中, 需要监测用来写入数据的文件描述符放入writefds集合中。exceptfds集合用来存放需要被监测是否有特殊情况发生的文件描述符,如TCP out-of-band data。nfds必须被赋值为这三个集合中最大的那个文件描述符的值加1。如果某个集合为空,可以用NULL来表示。可以使用下面这些函数来把需要监测的文件描述符从集合中清除和添加,或清空整个集合。

 

void FD_CLR(int fd, fd_set *set); /* 清除 */
void FD_SET(int fd, fd_set *set); /* 添加 */
void FD_ZERO(fd_set *set); /* 清空 */

      最后一个参数timeout如果被赋值为NULL,表示select调用将一直处于阻塞状态直到有一个被监测的文件描述符准备就绪。否则需要准备一个timeval结构,把需要等待的时间赋给它,表示在一定时间内如果没有文件描述符就绪,select也将返回。timeval结构如下:

 

struct timeval 
{
        long    tv_sec;         /* 秒 */
        long    tv_usec;        /* 微秒 */
};

      如果select执行成功,readfds、writefds和exceptfds将会包含已准备就绪的文件描述符,并返回这些文件描述符的总数。如果执行失败则返回-1。

      执行成功后,可以通过如下函数判断某个文件描述符是否在某个集合中。

 

int  FD_ISSET(int fd, fd_set *set); /* 判断是否存在 */

 

      最后附上一个使用select系统调用的TCP代理程序源代码:

#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define BUFSIZE 1024

int accpet_connection(const char *ipaddr, uint16_t port);
int connect_remote(const char *ipaddr, uint16_t port);

int main(int argc, const char *argv[])
{
	char buf[BUFSIZE];

	if (argc < 5)
	{
		printf("usage: %s localaddr localport remoteaddr remoteport \n", argv[0]);
		return -1;
	}

	uint16_t localport = (uint16_t)atoi(argv[2]);
	uint16_t remoteport = (uint16_t)atoi(argv[4]);
	
	fd_set rmask;
	fd_set wmask;

	FD_ZERO(&rmask);
	FD_ZERO(&wmask);

	int accpfd, remotfd;

	printf("Now you can connect %s:%d, it will be redirected to %s:%d\n", argv[1], localport, argv[3], remoteport);

	if ((accpfd = accpet_connection(argv[1], localport)) < 0)
		return -1;

	if ((remotfd = connect_remote(argv[3], remoteport)) < 0)
		return -1;


	for (;;)
	{
		FD_SET(accpfd, &rmask);
		FD_SET(remotfd, &rmask);

		int fd_count;

		if ((fd_count = select(MAX(accpfd, remotfd) + 1, &rmask, &wmask, NULL, NULL)) < 0)
		{
			perror("select error");
			return -1;
		}

		ssize_t accplen;
		ssize_t remotlen;

		if (fd_count > 0)
		{
			if (FD_ISSET(accpfd, &rmask))
			{
				while ((accplen = read(accpfd, buf, BUFSIZE)) > 0)
				{
					printf("Get some data from client %s\n", argv[1]);
					write(remotfd, buf, accplen);
				}
			}

			if (FD_ISSET(remotfd, &rmask))
			{
				while ((remotlen = read(remotfd, buf, BUFSIZE)) > 0)
				{
					printf("Get some data from remote %s\n", argv[3]);
					write(accpfd, buf, remotlen);
				}
			}

			/* 对方已关闭socket连接 */
			if (0 == accplen || 0 == remotlen)
				return 0;
		}
	}

	return 0;
}

int accpet_connection(const char *ipaddr, uint16_t port)
{
	int listenfd, connfd;
	struct sockaddr_in servaddr;

	if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket error");
		return -1;
	}

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);

	if (inet_pton(AF_INET, ipaddr, &servaddr.sin_addr) <= 0)
	{
		perror("inet_pton error");
		return -1;
	}

	if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
	{
		perror("bind error");
		return -1;
	}

	if (listen(listenfd, 5) < 0)
	{
		perror("listen error");
		return -1;
	}

	if ((connfd = accept(listenfd, NULL, NULL)) < 0)
	{
		perror("accpet error");
		return -1;
	}

	/*
	 * 设置文件描述符为非阻塞模式。
	 */
	int flags;
    if ((flags = fcntl(connfd, F_GETFL, 0)) < 0)
	{
		perror("fcntl get flags error");
		return -1;
	}

    if (fcntl(connfd, F_SETFL, flags | O_NONBLOCK) < 0)
	{
		perror("fcntl set O_NONBLOCK error");
		return -1;
	}

	return connfd;
}

int connect_remote(const char *ipaddr, uint16_t port)
{
	int sockfd;
	struct sockaddr_in servaddr;

	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket error");
		return -1;
	}

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
	if (inet_pton(AF_INET, ipaddr, &servaddr.sin_addr) <= 0)
	{
		perror("inet_pton error");
		return -1;
	}

	if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
	{
		perror("connect error");
		return -1;
	}

	/*
	 * 设置文件描述符为非阻塞模式。
	 */
	int flags;
    if ((flags = fcntl(sockfd, F_GETFL, 0)) < 0)
	{
		perror("fcntl get flags error");
		return -1;
	}

    if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0)
	{
		perror("fcntl set O_NONBLOCK error");
		return -1;
	}

	return sockfd;
}
 

     以及一个用做测试的回写字符串的TCP服务脚本:

 

#!/usr/bin/perl

use strict;
use warnings;
use IO::Socket::INET;

$SIG{CHLD} = 'IGNORE';

my $sock = IO::Socket::INET->new(Listen => 5,
                                 LocalAddr => '192.168.1.100',
                                 LocalPort => 9999,
                                 Proto     => 'tcp');

while (1)
{
	my $client = $sock->accept() or do {next;};

	my $pid = fork;

	if (0 == $pid)
	{
		$client->autoflush(1);
		while (<$client>)
		{
			print $client $_;
		}
		exit 0;
	}

	close $client;
}
分享到:
评论

相关推荐

    TCPIP详解--共三卷

    11.4 一个简单的例子 110 11.5 IP分片 111 11.6 ICMP不可达差错(需要分片) 113 11.7 用Traceroute确定路径MTU 114 11.8 采用UDP的路径MTU发现 116 11.9 UDP和ARP之间的交互作用 118 11.10 最大UDP数据报长度 119 ...

    TCP-IP详解卷2:实现.part1

    本书适用于希望理解TCP/TP协议如何实现的人,包括编写网络应用程序的程序员以及利用TCP/IP维护计算机网络的系统管理员。 目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 ...

    TCP_IP详解卷1

    该文件共分12个压缩包,必须下载到同一个文件夹后解压才可以用哦~~ 简介: 《TCP/IP详解,卷1:协议》是一本完整而详细的TCP/IP协议指南。描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用...

    TCP-IP详解-卷2实现分两部分-part2

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

    TCP/IP详解part_2

    该文件共分12个压缩包,必须下载到同一个文件夹后解压。 简介: 《TCP/IP详解,卷1:协议》是一本完整而详细的TCP/IP协议指南。描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence ...

    select模型 socket

    这段描述介绍了一个基于Windows平台上的简单回显服务器示例程序,该程序利用Winsock库并通过`select()`API实现了I/O多路复用功能。具体来说: 1. **程序实现**:该程序被设计为控制台应用程序,能够在控制台上打印...

    TCP-IP详解卷二:实现

    第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统调用 421 16.13.1 selscan函数 425...

    TCP-IP详解卷2

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

    TCP-IP详解卷2:实现

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

    本资源分为两个压缩包,请注意:TCP-IP详解卷2:实现(2)

    本书适用于希望理解TCP/TP协议如何实现的人,包括编写网络应用程序的程序员以及利用TCP/IP维护计算机网络的系统管理员。 目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1...

    TCP-IP详解卷2:实现.part2

    本书适用于希望理解TCP/TP协议如何实现的人,包括编写网络应用程序的程序员以及利用TCP/IP维护计算机网络的系统管理员。 目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 ...

    本资源分为两个压缩包,请注意:TCP-IP详解卷2:实现(1)

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

    TCP-IP详解卷2_2.rar

    13.8 离开一个组:igmp_leavegroup函数 314 13.9 小结 315 第14章 IP多播选路 316 14.1 引言 316 14.2 代码介绍 316 14.2.1 全局变量 316 14.2.2 统计量 317 14.2.3 SNMP变量 317 14.3 多播输出处理(续) 317 14.4 ...

    tcp-ip详解2:协议的实现

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

    TCP-IP详解卷2_1.rar

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

    TCP-IP详解卷二:实现part2

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

    TCP-IP详解卷2:实现——2

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

    TCP-IP详解卷2:实现——1

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

    TCP-IP详解卷2:实现.rar

    目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 ...16.13 select系统...

Global site tag (gtag.js) - Google Analytics