socket代码



2010-04-18 16:34:09


#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>


#include <pthread.h>


#define MYPORT 1234//服务端端口

#define BACKLOG 5//最大连接数

#define BUF_SIZE 200


void mySocket() {

	puts("server start....");

	//建立一个socket通信

	int sockFd;

	if ((sockFd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

		perror("socket");

		exit(1);

	}

	puts("建立一个socket通信");

	/*int socket(int domain,int type,int protocol);

	socket()用来建立一个新的socket,也就是向系统注册,

	通知系统建立一通信端口。参数 domain 指定使用何种的地址类型,

	完整的定义在/usr/include/bits/socket.h 内

	domain:

	PF_UNIX/PF_LOCAL/AF_UNIX/AF_LOCAL UNIX 进程通信协议

	PF_INET?AF_INET Ipv4网络协议

	PF_INET6/AF_INET6 Ipv6 网络协议

	PF_IPX/AF_IPX IPX-Novell协议

	PF_NETLINK/AF_NETLINK 核心用户接口装置

	PF_X25/AF_X25 ITU-T X.25/ISO-8208 协议

	PF_AX25/AF_AX25 业余无线AX.25协议

	PF_ATMPVC/AF_ATMPVC 存取原始ATM PVCs

	PF_APPLETALK/AF_APPLETALK appletalk(DDP)协议

	PF_PACKET/AF_PACKET 初级封包接口

	type:

	SOCK_STREAM 提供双向连续且可信赖的数据流,即TCP。支持

	OOB 机制,在所有数据传送前必须使用connect()来建立连线状态。

	SOCK_DGRAM 使用不连续不可信赖的数据包连接

	SOCK_SEQPACKET 提供连续可信赖的数据包连接

	SOCK_RAW 提供原始网络协议存取

	SOCK_RDM 提供可信赖的数据包连接

	SOCK_PACKET 提供和网络驱动程序直接通信。

	protocol:

	用来指定socket所使用的传输协议编号,通常此参考不用管它,设为0即可。

	返回值

	成功则返回socket处理代码,失败返回-1。

	*/


	//设置套接口的选项

	int yes = 1;

	if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {

		perror("setsockopt");

		exit(1);

	}

	puts("设置套接口的选项");

	/*

	int setsockopt(int s,int level,int optname,const void * optval,socklen_toptlen);

	s所指定的socket状态。参数level代表欲设置的网络层,一般设成SOL_SOCKET以存取socket层。参数optname代表欲设置的选项,有下列几种数值:

	SO_DEBUG 打开或关闭排错模式

	SO_REUSEADDR 允许在bind()过程中本地地址可重复使用

	SO_TYPE 返回socket形态。

	SO_ERROR 返回socket已发生的错误原因

	SO_DONTROUTE 送出的数据包不要利用路由设备来传输。

	SO_BROADCAST 使用广播方式传送

	SO_SNDBUF 设置送出的暂存区大小

	SO_RCVBUF 设置接收的暂存区大小

	SO_KEEPALIVE 定期确定连线是否已终止。

	SO_OOBINLINE 当接收到OOB 数据时会马上送至标准输入设备

	SO_LINGER 确保数据安全且可靠的传送出去。

	参数

	optval代表欲设置的值,参数optlen则为optval的长度。

	返回值

	成功则返回0,若有错误则返回-1,错误原因存于errno。

	setsockopt什么时候可用

	如果是默认的简单TCP数据传输或者UDP广播的发送,不使用setsockopt完全没有问题,但是如果要进行更详细的设置(比如指定接收缓冲区的大小,指定传输时是否采用Nagle算法),则必须使用setsockopt。

	*/


	//Socket配置

	struct sockaddr_in serverAddr;

	/*

	struct sockaddr_in {

  	  short int sin_family;//sin_family指代协议族,在socket编程中只能是AF_INET

  	  unsigned short int sin_port;//sin_port存储端口号(使用网络字节顺序)

  	  struct in_addr sin_addr;//sin_addr存储IP地址,使用in_addr这个数据结构

  	  unsigned char sin_zero[8];//sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。

  };


  typedef struct in_addr {

  	  union {

  		struct{

  			unsigned char s_b1,s_b2,s_b3,s_b4;

  			} S_un_b;

  			struct {

  			unsigned short s_w1,s_w2;

  			} S_un_w;

 			unsigned long S_addr;//s_addr按照网络字节顺序存储IP地址

  		} S_un;

  } IN_ADDR;

	*/

	serverAddr.sin_family = AF_INET;


	serverAddr.sin_port = htons(MYPORT);//htons函数用来转换u_short了来自主机的TCP / IP网络字节顺序

	serverAddr.sin_addr.s_addr = INADDR_ANY;//INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。

	bzero(&(serverAddr.sin_zero), 8);

	//extern void bzero(void *s, int n);

	//memset(server_addr.sin_zero, '/0', sizeof(serverAddr.sin_zero));

	//置字节字符串s的前n个字节为零。

	//bzero与memset比较,数组小的时候bzero性能高,数组越大memset性能越高

	puts("socket配置");


	//Socket绑定

	if (bind(sockFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {

		perror("bind");

		exit(1);

	}

	puts("socket绑定");

	/*

	bind(对socket定位)

	int bind(int sockfd, struct sockaddr * my_addr, int addrlen);

	强制转换

	 参数addrlen为sockaddr的结构长度。

	返回值成功则返回0,失败返回-1,错误原因存于errno中。

	*/


	//监听

	if (listen(sockFd, BACKLOG) == -1) {

		perror("listen");

		exit(1);

	}

	puts("监听");

	/*

	int listen(int s,int backlog);

	listen()用来等待参数s 的socket连线。

	参数backlog指定同时能处理的最大连接要求,如果连接数目达此上限则client端将收到ECONNREFUSED的错误。

	Listen()并未开始接收连线,只是设置socket为listen模式,真正接收client端连线的是accept()。

	通常listen()会在socket(),bind()之后调用,接着才调用accept()。

	 返回值	成功则返回0,失败返回-1,错误原因存于errno

	  附加说明

	listen()只适用SOCK_STREAM或SOCK_SEQPACKET的socket类型。如果socket为AF_INET则参数 backlog 最大值可设至128。

	*/


	socklen_t sinSize;

	struct sockaddr_in clientAddr;

	sinSize = sizeof(clientAddr);


	int maxSock;

	maxSock = sockFd;

	struct timeval timeOut;

	timeOut.tv_sec = 3;//select等待,要非阻塞就置0

	timeOut.tv_usec = 0;


	fd_set fdsr;

	int ret;

	int newSockFd = 0;

	char buf[BUF_SIZE];


	fflush(NULL);//清空缓冲区

	while (true) {

		FD_ZERO(&fdsr);//每次循环都要清空集合,否则不能检测描述符变化

		FD_SET(sockFd, &fdsr);//添加描述符


		ret = select(maxSock + 1, &fdsr, &fdsr, NULL, &timeOut);

		if (ret < 0) {

			perror("select");

			break;

		} else if (ret == 0) {

			puts("timeout/n");

			continue;

		}

		/*

		非阻塞方式,select()

		int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

		int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!

	  	fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,

		即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,

		表示有文件可读,如果没有可读的文件,则根据 timeout参数再判断是否超时,若超出timeout的时间,select返回0,

		若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

	  	fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,

		即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0 的值,

		表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,

		若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

	  	fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。

	  	struct timeval *timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,

		第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;

		第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;

		第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

	  	返回值:

	  	负值:select错误

		正值:某些文件可读写或出错

		0:等待超时,没有可读写或错误的文件

		*/


		if (FD_ISSET(sockFd, &fdsr)) {

			newSockFd = accept(sockFd, (struct sockaddr*)&clientAddr, &sinSize);

			if (newSockFd <= 0) {

				perror("accept");

				continue;

			}

			printf("一个客户端连接:%s/n", inet_ntoa(clientAddr.sin_addr));

		}

		/*

		int accept(int s,struct sockaddr * addr,int * addrlen);

		accept()用来接受参数s的socket连线。

		addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。

		addrlen:(可选)指针,指向存有addr地址长度的整形数。

		返回值

		成功则返回新的socket处理代码,失败返回-1,错误原因存于errno中。

		*/


		//接收数据

		int recvRet;

		recvRet = recv(newSockFd, buf, sizeof(buf), 0);

		printf("客户端数据:%s/n", buf);

		/*

		int recv(int s,void *buf,int len,unsigned int flags);

		recv()用来接收远端主机经指定的socket传来的数据,并把数据存到由参数buf 指向的内存空间,参数len为可接收数据的最大长度。

		参数

		flags一般设0。其他数值定义如下:

		MSG_OOB 接收以out-of-band 送出的数据。

		MSG_PEEK 返回来的数据并不会在系统内删除,如果再调用recv()会返回相同的数据内容。

		MSG_WAITALL强迫接收到len大小的数据后才能返回,除非有错误或信号产生。

		MSG_NOSIGNAL此操作不愿被SIGPIPE信号中断返回值成功则返回接收到的字符数,失败返回-1,错误原因存于errno中。

		*/



		char* msg = "Hello World";

		int len = strlen(msg);

		int successLen = send(newSockFd, msg, len, 0);

		printf("实际传输的字符数:%i/n", successLen);

		fflush(NULL);

		/**

		int send(int s,const void * msg,int len,unsigned int falgs);

		参数s为已建立好连接的socket。

		参数msg指向欲连线的数据内容,参数len则为数据长度。

		参数flags一般设0,其他数值定义如下

		MSG_OOB 传送的数据以out-of-band 送出。

		MSG_DONTROUTE 取消路由表查询

		MSG_DONTWAIT 设置为不可阻断运作

		MSG_NOSIGNAL 此动作不愿被SIGPIPE 信号中断。

		 返回值

		成功则返回实际传送出去的字符数,失败返回-1。错误原因存于errno

		*/

	}

	//关闭Socket

	close(sockFd);

}