Linux 10 TCP/UDP socket基本用法

面向TCP连接的socket通信程序:

服务端:创建套接字,指定协议族(sockaddr_in),绑定,监听(listen),接受链接(accept),发送或接收数据;客户端:创建套接字,指定协议族,连接,发送或接收数据
这几个步骤都是必须的。
补充:在发送和接受数据时:write/send/sendto,read/recv/recvfrom都可以用,通常会用:send,recv;但需要注意的是:在面向UDP的socket程序中,发送数据时,如果用sendto的话,就不用connect了;但是,在面向TCP的程序中,在发送数据时,即使sendto,也必须connect,也就是说connect这一步是必不可少的。

面向UDP连接的socket通信程序:

服务端:创建套接字,指定协议族(sockaddr_in),绑定(不需要listen和accept),发送或接收数据;客户端:创建套接字,指定协议族,连接(和TCP的客户端步骤一样),发送或接收数据。
补充:在发送和接收数据时,和TCP大同小异,write/send/sendto,read/recv/recvfrom都可以用,但UDP通常会用sendto,recvfrom;需要注意的是:当用sendto发送数据的时候,就不用connect了(用了也没事),其他的(write,send)必须connect。

对于已连接udp套接口(调用过connect),与缺省的未连接udp套接口相比,发生了三个变化:

1.不能给输出操作指定目的ip地址和套接口。也就是说,不能使用sendto(),而改写write或send。写到已连接udp套接口上的任何内容都自动发送到connect指定的协议地址。
2.不能使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg。在一个已连接udp套接口上由内核为输入操作返回的数据报仅仅是那些来自connect所指定协议地址的数据报。目的地为这个已连接udp套接口的本地协议,发源地却不是该套接口早先connect到的协议地址的数据报,不会投递到该套接口。这样就限制一个已连接udp套接口能且仅能与一个对端交换数据报。
3.由已连接udp套接口引发的异步错误返回给它们所在的进程。相反,未连接udp套接口不接受任何异步错误。
拥有一个已连接udp套接口的进程可为下列两个目的之一再次调用connect:
A.指定新的ip地址和端口号
B.断开套接口。
为了断开一个已连接udp套接口,再次调用connect时把套接口地址结构的地址族成员设置为AF_UNSPEC。这么做可能返回一个EAFNOSUPPORT错误,不过没关系。使得套接口断开连接的是在已连接udp套接口上调用connect的进程。
已连接udp套接口还可用来确定用于某个特定目的地的外出接口。这是有connect函数应用到udp套接口时的一个副作用造成的:内核选择本地ip地址(假设其进程未曾调用bind显式指派它)。这个本地ip地址通过为目的ip地址搜索路由表得到外出接口,然后选用该接口的主ip地址而选定。在udp套接口上调用connect并不给对端主机发送任何信息,它完全是一个本地操作,只是保存对端的ip地址和端口号。在一个未绑定端口号的udp套接口上调用connect同时也给该套接口指派一个临时端口。

阻塞模式补充:

无论是TCP还是UDP,默认情况下创建的都是阻塞模式(blocking)的套接字,执行到accept,connect,write/send/sendto,read/recv/recvfrom等语句时,会一直等待(connect有点例外,它连接一段时间,如果连接不成功,会以错误形式返回,不会一直等待)。
可以把socket设置成非阻塞模式,linux下用fcntl函数,windows下用的是ioctlsocket函数。(TCP和UDP设置成非阻塞模式以后,效果是一样的,都不再等待,而是立即返回。只是sendto和send一次发送的最大数据量可能不同,两种模式下返回的错误代码应该也是相同的)
设置成非阻塞模式以后,这些函数不再等待会立即返回(这和windows下是相同的),至于错误时返回的值应该也是和windows下相同的(具体没试,send和recv在windows错误时返回的值,请看 “套接字的同步阻塞(blocking)与异步非阻塞(no blocking)”)。
TCP面向连接,UDP面向无连接(在默认的阻塞模式下):
read/recv/recvfrom:
当客户端退出程序或断开连接时,TCP的这个函数会立即返回不再阻塞(因为服务端自己知道客户端已经退出或断开连接,证明它是面向连接的),而UDP的这个函数将会始终保持阻塞(因为服务端自己不知道客户端已经退出或断开连接,证明它是面向无连接的)。
TCP无边界,UDP有边界(在默认的阻塞模式下):
read/recv/recvfrom:TCP,客户端连续发送数据,只要服务端的这个函数的缓冲区足够大,会一次性接收过来(客户端是分好几次发过来,是有边界的,而服务端却一次性接收过来,所以证明是无边界的);UDP:客户端连续发送数据,即使服务端的这个函数的缓冲区足够大,也只会一次一次的接收,发送多少次接收多少次(客户端分几次发送过来,服务端就必须按几次接收,从而证明,这种UDP的通讯模式是有边界的)。

补充(来自网络):

1.socket()的参数不同
2.UDP Server不需要调用listen和accept
3.UDP收发数据用sendto/recvfrom函数
4.UDP:shutdown函数无效
5.TCP:地址信息在connect/accept时确定
UDP:在sendto/recvfrom函数中每次均需指定地址信息
Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址。
sendto()函数原型为:
  int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
  该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。如果利用udp协议则不需经过连接操作。
Recvfrom()函数原型为:
  int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。如果from参数是一个空指针,那么相应的长度参数fromlen也必须是一个空指针,表示我们并不关心数据发送者的协议地址。
如果你对UDP模式的socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。(这一点正说明了我在“面向UDP连接的socket通信程序”中所补充的内容)

总结:

从sendto和recvfrom的后两个参数想到的:当客户端向服务端发送数据时,客户端必须知道服务端的IP地址和端口号,而结构体sockaddr_in正是完成了这项工作。所以在客户端程序中,一定要指定需要连接的服务端的IP地址和端口号(TCP中必须指定,因为它是面向连接的,交换数据前必须connect,而connect时就必须用到那个结构体;UDP可以不指定,那就需要让服务端先发送数据到客户端,客户端通过recvfrom函数接收,从而通过参数from得到了存放服务端IP地址和端口号的那个结构体,然后就可以通过它来交换数据了,后来一想这样不行,因为服务端向客户端发送数据时,在sendto中要指定客户端的IP地址和端口号,这个就又需要客户端也创建一个套接字打开一个端口并把它们绑定在一起接受连接。从而,得出的结论是:UDP中:无论服务端还是客户端,开始时,发送数据方一定要在程序中指定接收数据方的IP和端口,接收方通过recvfrom得到数据以后也就得到了发送方的IP和端口就可以通过这个结构体发送数据了实现了数据的双向传递(好像不对,UDP面向无连接,这个连接有可能过一会就断掉了,更重要的是,发送方根本就没有和socket绑定,也就是说那个端口是临时分配的,所以我认为即使成功也是偶然,有待证明);TCP不同:它是必须在客户端指定服务端的IP和端口,建立连接,然后两者可以任意接收和发送数据,实现双向数据传递)。
另外:
TCP在运行客户端connect之前必须先运行服务端,不然的话connect的连接会出错;UDP不一样(客户端发送信息,服务端接收信息,服务端需要绑定,客户端就需要指定服务端的IP和端口),先运行客户端和后运行客户端一样通信,只是当先运行客户端后运行服务端时,从客户端起来到服务端起来的这段时间内发送的数据服务端就收不到了(这也从另个方面体现了TCP的面向连接和UDP的面向无连接)。还有一点需要注意:UDP中,客户端先运行,用connect+write/send/sendto:在服务端起来之前不发送数据过去,起来之后,和后运行客户端收发数据一样,在服务端起来之前发送数据了,那么服务端起来之后,客户端第一次发送的数据,服务端会收不到,再发就一样了;用sendto(不用connect)的话,则不管服务端起来之前发没发数据,服务端起来之后都照常收发数据,从客户端起来到服务端起来的这段时间内发送的数据服务端就收不到了(这一点,或许是和上面所说的“如果你对UDP模式的socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息”有关,有待进一步证明)。
最后再补充一个小知识点:sizeof()这个函数,参数是一个变量时根本不用括号(用的话也行),只要用空格隔开即可;但是如果是一个数据类型的话,则必须用括号。

Select:

#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1(window中无效,仅作兼容使用)
readfds如果该套接口正处于监听listen()状态,则若有连接请求到达,该套接口便被标识为可读,这样一个accept()调用保证可以无阻塞完成。对其他套接口而言,可读性意味着有排队数据供读取。或者对于SOCK_STREAM类型套接口来说,相对于该套接口的虚套接口已关闭,于是recv()或recvfrom()操作均能无阻塞完成。如果虚电路被“优雅地”中止,则recv()不读取数据立即返回;如果虚电路被强制复位,则recv()将以WSAECONNRESET错误立即返回。如果SO_OOBINLINE选项被设置,则将检查带外数据是否存在(参见setsockopt())
writefds参数标识等待可写性检查的套接口。如果一个套接口正在connect()连接(非阻塞),可写性意味着连接顺利建立。如果套接口并未处于connect()调用中,可写性意味着send()和sendto()调用将无阻塞完成。〔但并未指出这个保证在多长时间内有效,特别是在多线程环境中〕。
exceptfds参数标识等待带外数据存在性或意味错误条件检查的套接口。请注意如果设置了SO_OOBINLINE选项为假FALSE,则只能用这种方法来检查带外数据的存在与否。对于SO_STREAM类型套接口,远端造成的连接中止和KEEPALIVE错误都将被作为意味出错。如果套接口正在进行连接connect()(非阻塞方式),则连接试图的失败将会表现在exceptfds参数中。
如果对readfds、writefds或exceptfds中任一个组类不感兴趣,可将它置为空NULL。
底下的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set* set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set* set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set* set); 用来清除描述词组set的全部位
timeout为结构timeval,用来设置select()的等待时间,其结构定义如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
如果参数timeout设为NULL则表示select()没有timeout,一直等待有一个套接字准备好,如果timeval{0,0},表示立即返回执行,是一个非阻塞函数,如果timeval大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值0
执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1。
Select只是检测各个socket的变化,并不进行具体的accept,read,write等操作。
常见的程序片段:fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset){……}

Select example:
//Select.c
#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>

/******************************************
*  函数名:main
*函数功能:读取终端键盘的输入,输入'q'退出
*输    入:无
*输    出:无
*  返回值:无
*修改日期:2017-03-15
*修改作者:zzj
*******************************************/
int main ()
{
	int keyboard;
	int ret,i;
	char c;
	fd_set readfd;
	struct timeval timeout;
	keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
	assert(keyboard>0);
	while(1)
	{
		timeout.tv_sec=1;
		timeout.tv_usec=0;
		FD_ZERO(&readfd);
		FD_SET(keyboard,&readfd);
		ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
		if(FD_ISSET(keyboard,&readfd))
		{
			i=read(keyboard,&c,1);
			if('\n'==c)
				continue;
			printf("your input is %c\n",c);
     
			if ('q'==c)
				break;
		}
	}
}

UDP Example:
/*ucpClient.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>

#define MAXLINE 80
#define SERV_PORT 8888

void do_cli(FILE *fp, int sockfd, struct sockaddr *pservAddr, socklen_t servLen)
{
	int recvLen;
	char sendBuf[MAXLINE];
	char recvBuf[MAXLINE];
	
	printf("please input message to send:\n");
	while(fgets(sendBuf, MAXLINE, fp) != NULL)
	{
		sendto(sockfd, sendBuf, strlen(sendBuf), 0, pservAddr, servLen);
		recvLen = recvfrom(sockfd, recvBuf, MAXLINE, 0, NULL, NULL);
		if(recvLen == -1)
		{
			perror("recvfrom error");
			exit(1);
		}
		recvBuf[recvLen] = 0;
		printf("message from server reply:\n");
		fputs(recvBuf, stdout);
	}
}

int main(int argc, char **argv)
{
	int sockfd;
	struct sockaddr_in servAddr;

	if(argc != 2)
	{
		printf("usage: udpclient <IPaddress>\n");
		exit(1);
	}

	/*init sockaddr_in & set server addr and port*/
	bzero(&servAddr, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(SERV_PORT);
	if(inet_pton(AF_INET, argv[1], &servAddr.sin_addr) <= 0)
	{
		printf("[%s] is not a valid IPaddress\n", argv[1]);
		exit(1);
	}

	/*create udp socket*/
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	do_cli(stdin, sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr));

	return 0;
}
/*udpServer.c*/
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 80
#define SERV_PORT 8888

void do_echo(int sockfd, struct sockaddr * cliAddr, socklen_t cliLen)
{
	int recvLen;
	int sockLen;
	char recvBuf[MAXLINE];
	char sendBuf[MAXLINE] = "server reply message!";

	while(1)
	{
		sockLen = cliLen;

		recvLen = recvfrom(sockfd, recvBuf, MAXLINE, 0, cliAddr, &sockLen);
		if(recvLen > 0)
		{
			recvBuf[recvLen] = 0;
			printf("message from client is:\n%s\n",recvBuf);
			sendto(sockfd, sendBuf, strlen(sendBuf), 0, cliAddr, sockLen);
		}
	}
}

int main(void)
{
	int sockfd;
	struct sockaddr_in servAddr;
	struct sockaddr_in cliAddr;

	/*create udp socket*/
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	/*init sockaddr_in & set address_family addr and port*/
	bzero(&servAddr, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAddr.sin_port = htons(SERV_PORT);

	/*bind ipaddr and port to socket sockfd*/
	if(bind(sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
	{
		perror("bind error");
		exit(1);
	}

	/*call echo function*/
	do_echo(sockfd, (struct sockaddr *)&cliAddr, sizeof(cliAddr));

	return 0;
}
TCP Example
/*tcpClient.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define TCP_BUFFER_SIZE         128
#define TCP_SERVER_PORTNUM      6000

void str_cli(FILE *fp,int sockfd)
{
	char sendBuf[TCP_BUFFER_SIZE];
	char recvBuf[TCP_BUFFER_SIZE+1];
	int recvLen;
	printf("please input message:\n");
	
	while(fgets(sendBuf,TCP_BUFFER_SIZE,fp) != NULL)
	{
		/*send message to server*/
		if(send(sockfd,sendBuf,strlen(sendBuf),0) == -1)
		{
			perror("send");
			exit(1);
		}

		/*recv reply message from server*/
		recvLen = read(sockfd,recvBuf,TCP_BUFFER_SIZE);
		if(recvLen <= 0)
		{
			perror("read");
			exit(1);
		}
		recvBuf[recvLen] = 0;
		printf("message from server is:\n");
		fputs(recvBuf,stdout);
	}
}

int main(int argc,char **argv)
{
	struct sockaddr_in serverAddr;  
	int clientSocket;

	if(argc != 2)
	{
		printf("usage:tcpClient <IPaddress>\n");
		exit(1);
	}

	/*create tcp socket*/
	if((clientSocket=socket(AF_INET,SOCK_STREAM,0)) == -1)
	{
		perror("socket");
		exit(1);
	}

	/*init sockaddr_in & set server address and port*/
	bzero(&serverAddr, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(TCP_SERVER_PORTNUM);
	inet_pton(AF_INET,argv[1],&serverAddr.sin_addr);

	/*connect to server*/
	if(connect(clientSocket,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)
	{
		perror("connect");
		exit(1);
	}

	str_cli(stdin,clientSocket);

	return 0;
}
/*tcpServer.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define TCP_SERVER_MAXCON       32
#define TCP_BUFFER_SIZE         128
#define TCP_SERVER_PORTNUM      6000

void str_echo(int sockfd)
{
	int recvLen;
	char recvBuf[TCP_BUFFER_SIZE];
	char sendBuf[TCP_BUFFER_SIZE] = "receive success,thank you,client!";
	while(recvLen <= 0)
	{
		recvLen = recv(sockfd,recvBuf,TCP_BUFFER_SIZE,0);
		if(recvLen > 0 )
		{
			recvBuf[recvLen] = 0;
			printf("message from client is:\n%s\n",recvBuf);
			write(sockfd,sendBuf,strlen(sendBuf));
			recvLen = 0;
		}
	}
}

int main(int argc,char **argv)
{
	struct sockaddr_in clientAddr;
	struct sockaddr_in serverAddr;
	int serverSocket,clientFd;
	int cliLen;
	int childpid;

	/*init sockaddr_in & set addr and listening port*/
	bzero(&serverAddr,sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(TCP_SERVER_PORTNUM);
	serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	/*create tcp socket*/
	if((serverSocket=socket(AF_INET,SOCK_STREAM,0)) == -1)
	{
		perror("socket");
		exit(1);
	}
	printf("\nsocket success! socket = %d\n",serverSocket);

	/*bind addr and port to socket*/
	if(bind(serverSocket,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)
	{
		perror("bind");
		exit(1);
	}
	printf("bind success!\n");

	/*listening to port,waiting for connection*/
	if(listen(serverSocket,TCP_SERVER_MAXCON) == -1)
	{
		perror("listen");
		exit(1);
	}
	printf("server listening......\n");
	
	while(1)
	{
		cliLen = sizeof(clientAddr);

		/*accept connection*/
		clientFd=accept(serverSocket,(struct sockaddr *)&clientAddr,&cliLen);
		if((childpid = fork()) == 0)
		{
			close(serverSocket);
			str_echo(clientFd);
			exit(0);
		}
		close(clientFd);
	}
	return 0;
}

Makefile:

all:tcp_server tcp_client udp_server udp_client select clean.o

tcp_server: tcp_server.o 
	gcc -o tcp_server tcp_server.o 
tcp_client: tcp_client.o 
	gcc -o tcp_client tcp_client.o 
udp_server: udp_server.o
	gcc -o udp_server udp_server.o
udp_client: udp_client.o
	gcc -o udp_client udp_client.o
select: select.o
	gcc -o select select.o

clean.o:
	rm -f *.o
%.o:%.c
	gcc -c $< -o $@ 
clean: 
	rm -f *server *client *.o *select

猜你喜欢

转载自blog.csdn.net/zzj244392657/article/details/92573487
今日推荐