socket编程(一):TCP/UDP基础篇


关于 socket的编程,有很多知识。本文讲述一下这部分比较基础的点。

1. 关键的结构体

在说sockaddr_in之前,我觉得有必要列出来sockaddr这个结构体。

1.1. sockaddr

sockaddr是通用的socket地址,其定义在#include <sys/socket.h>中。

struct sockaddr
{
	sa_family_t		sin_family;	//地址族,一般使用AF_INET
	char			sa_data[14]; //14 bytes 协议地址
};

此结构体用作bind,connect, recvfrom, sendto等函数的参数中,以指明地址信息。但一般编程并不会使用它,而是使用等价的结构体sockaddr_in

1.2. sockaddr_in

看过一点相关代码的朋友,一定对这组结构体不陌生:

struct sockaddr_in
{
	sa_family_t		sin_family;		//地址族,一般使用AF_INET
	unit16_t		sin_port;		//16位TCP/UDP端口号
	struct in_addr	sin_addr;		//32位IP地址
	char			sin_zero[8];	//一般不使用
};

而其中包含的结构体定义为:

struct in_addr
{
	In_addr_t	s_addr;		//32位IPv4地址
};

sockaddr_in在头文件#include<netinet/in.h>#include <arpa/inet.h>中定义,该结构体把portaddr 分开储存在两个变量中,这种方式解决了sockaddr的缺陷。

1.3. 说明

  • sin_family是地址族(Address Family),其格式位AF_XXXX,对于socket编程,全部使用AF_INET,这代表了TCP\UDP
  • sin_portsin_addr(s_addr)都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。
  • sin_zero虽然不怎么使用,但也要明白是干嘛的。其作用是为了使sockaddrsockaddr_in两个数据结构大小保持相同而保留的空字节。
  • sockaddr_insockaddr是并列的结构,指向sockaddr_in的结构体的指针也可以指向sockaddr的结构体,并代替它。也就是说,你可以使用sockaddr_in建立你所需要的信息, 然后用进行类型转换就可以了。

1.4. 初始化结构体

	#define MY_PORT 8888
    int mySocket;                        /*服务器套接字文件描述符*/
    struct sockaddr_in mySocket_addr;
    /*初始化地址*/
    bzero(&mySocket_addr, sizeof(struct sockaddr_in));      /*清零*/
    mySocket_addr.sin_family = AF_INET;                		/*AF_INET协议族*/
    mySocket_addr.sin_addr.s_addr = htonl(INADDR_ANY); 		/*任意本地地址*/
    mySocket_addr.sin_port = htons(MY_PORT);       			/*定义的端口*/

1.5. 初始化的注释

1.5.1. 端口号

端口是很重要的一个概念,我这里端口号8888只是我随意写的一个例子。端口是一种抽象的结构,包括数据结构和I/O缓冲区。
应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问之。
类似于文件描述符,每个端口都拥有一个叫端口号(port number)的整数型标识符,用于区别不同端口。

这是我找到的一个讲的很好的博文:TCP/UDP共用端口问题

1.5.2. htons(), htonl(), ntohs(), ntohl()函数说明

初始化的时候,用到了这两个函数htonshtonl,那么本节就说下这两个以及相关的函数。

函数 说明
uint16_t htons(uint16_t netshort) 此函数将将主机的无符号短整形数转换成网络字节
顺序字节顺序 ,返回值为一个网络字节顺序的值
uint32_t htonl(uint32_t hostlong) 将一个32位数从主机字节顺序转换成网络字节顺序,
返回一个网络字节顺序的值
uint16_t ntohs(uint16_t netshort) 将一个16位数由网络字节顺序转换为主机字节顺序,
返回一个以主机字节顺序表达的数
uint32_t ntohl(uint32_t netlong) 将一个32位数从网络字节顺序转换为主机字,
返回一个以主机字节顺序表达的数

我在进行初始化,自然要转地址位为网络地址,所以使用htonshtonl。而正如上面所说,sin_port是16位,s_addr是32位,因而如此分别使用:

    mySocket_addr.sin_addr.s_addr = htonl(INADDR_ANY); 		/*任意本地地址*/
    mySocket_addr.sin_port = htons(MY_PORT);       			/*定义的端口*/

在此需要说明,INADDR_ANY是指地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。当初始化,不确定地址时候,可以使用。如此将开启计算机所有IP地址/网卡等待接收信号。
打个比方,比如你的机器有三个ip:192.168.1.1 202.202.202.202 61.1.2.3。如果你预设的值为server.sin_addr.s_addr=inet_addr("192.168.1.1");,然后监听100端口。这时其他机器只有连接 192.168.1.1:100才能成功。 连接202.202.202.202:10061.1.2.3:100都会失败。 但是如果server.sin_addr.s_addr=htonl(INADDR_ANY); 的话,无论连接哪个ip都可以连上的。

1.5.3. inet_ntoa()inet_addr()

说到地址转化函数,这两个也需要提一下。

char *inet_ntoa(struct in_addr in)  
将一个32位网络字节序的二进制IP地址转换成相应的点分十进制的IP地址
in_addr_t inet_addr(const char *cp)
参数为一个点分十进制的IP地址,如果正确执行将返回一个无符号长整数型数。
如果传入的字符串不是一个合法的IP地址,将返回INADDR_NONE。






2. 建立连接并且通讯

这一节将说一下建立连接和通讯的相关函数。

2.1. 创建套接字: socket()

2.1.1. 函数说明

int socket( int af, int type, int protocol)
  • af:一个地址描述。仅支持AF_INET格式,即上面所说的mySocket_addr.sin_family = AF_INET
  • type:指定socket类型。新套接口的类型描述类型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。常用的socket类型有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等。
  • protocol:顾名思义,就是指定协议。套接口所用的协议。如调用者不想指定,可用0(一般都用0)。常用的协议有,IPPROTO_TCPIPPROTO_UDPIPPROTO_STCPIPPROTO_TIPC等,它们分别对应TCP传输协议UDP传输协议STCP传输协议TIPC传输协议

2.1.2. 举例

TCPSocket = socket(AF_INET, SOCK_STREAM, 0)  	//TCP面向字节流(SOCK_STREAM)
UDPSocket = socket(AF_INET, SOCK_DGRAM, 0)		//UDP面向报文(SOCK_DGRAM)

2.1.3. 适用范围

UDP/TCP 的 服务端/客户端 均需要

2.2. 绑定端口/IP: bind()

2.2.1. 函数说明

int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
  • sockfd是用socket()函数创建的文件描述符。
  • my_addr是指向一个结构为sockaddr参数的指针,sockaddr中包含了地址、端口和IP地址的信息。在进行地址绑定的时候,需要弦将地址结构中的IP地址、端口、类型等结构struct sockaddr中的域进行设置之后才能进行绑定,这样进行绑定后才能将套接字文件描述符与地址等接合在一起。
  • addrlenmy_addr结构的长度,可以设置成sizeof(struct sockaddr)。使用sizeof(struct sockaddr)来设置套接字的类型和其对已ing的结构。
  • 返回值为0时表示绑定成功,-1表示绑定失败,errno的错误值请自行百度。

2.2.2. 举例

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

2.2.3. 适用范围

适用于TCP\UDP的服务端
这里解释下为何只用于服务端:
首先说明原理,bind函数就是绑定, 将一个socket绑定到一个地址上, 也可以这么说:bind函数对一个socket进行命名(注意:socket名称包括三要素: 协议, ip, port)

  • 对于TCP的服务端bind()函数是一定需要的。可以做个小实验试下,如果不使用bind(),那么客户端在进行连接时候就会报错Connection refused。这很好理解,TCP是面向连接的,服务器是时时在监听(listen)有没有客户端的连接。如果服务器不绑定IP和端口的话(bind),客户端上线的时候怎么连到服务器呢。所以服务器要绑定IP和端口,而客户端就不需要了。

  • 对于TCP的客户端bind()一般是不需要的。客户端上线是主动向服务器发出请求的,因为服务器已经绑定了IP和端口,所以客户端上线的就向这个IP和端口发出请求,这时因为客户开始发数据了(发上线请求),系统就给客户端分配一个随机端口,这个端口和客户端的IP会随着上线请求一起发给服务器, 服务器收到上线请求后就可以从中获起发此请求的客户的IP和端口,接下来服务器就可以利用获起的IP和端口给客户端回应消息了。总之一句话:客户端是主动连接(connect), 而服务器是等待接受连接(accept)。

  • 对于UDP的服务端bind()也是需要的,原因同上。不过对于简单的例子,由于UDP是不面向连接的,所以我把bind()去掉,相当于和一般客户端一样,系统随机分配IP,还是可以运行的。但是如果这样的连接不仅仅又一个,这时候就会出现问题。随意还是必须需要的。

  • 对于UDP的客户端bind()是不需要的,虽然可以用,但真的没用。

  • 总结一下:

    1. 需要在建连前就知道端口的话,需要 bind 。
    2. 需要通过指定的端口来通讯的话,需要 bind。
    3. 无论对UDP还是TCP,服务端是需要预先指定端口进行通讯的,而且对于连接比较复杂的情况,如果不预先指定端口/IP并且绑定,那么后续通讯就会出问题,所以服务端一定需要bind()
    4. 对于客户端,使用bind()一般程序也没什么问题,但是说实话,没什么必要,这个事情交给内核去自动分配就好了。

    这一块,我是参考一些博文的,有兴趣的可以去看下原链接,并且自己尝试下。
    客户端 用不用 bind 的区别
    为什么在网络程序设计中服务器端必须使用bind函数来绑定IP地址和端口号,而客户端不需要使用bind函数来绑定IP地址和端口号呢?
    为什么TCP服务端需要调用bind函数而客户端通常不需要呢?

2.3. TCP的通信

众所周知,TCP是面向连接的,所以,在服务端和客户端互发信息之前,需要建立连接。

2.3.1. 进入监听状态: listen()

int listen(int sock, int backlog) 
  • 两个参数分别为:
    • sock :需要进入监听状态的套接字
    • backlog :请求队列的最大长度, i.e. 当有很多请求发生,会进入缓冲,这个缓冲长度也就是最大可接受多少请求,就是请求队列的最大长度。如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误,对于 Windows,客户端会收到 WSAECONNREFUSED 错误。
  • 对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态,再调用 accept() 函数,就可以随时响应客户端的请求了。
  • 所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。

2.3.2. 客户端尝试与服务端建立连接:connect()

int connect(int sockfd, struct sockaddr * serv_addr, int addrlen)
  • sockfd:标识一个套接字。
  • serv_addr:套接字建立连接的主机地址和端口号。
  • addrlenserv_addr的长度。
  • 返回值:成功则返回0, 失败返回-1, 错误原因存于errno 中 (各个参数与bind()一样)

2.3.3. 服务端接受客户端连接请求:accept()

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen)
  • 参数与 connect() 相同
  • accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。

2.3.4. 服务端/客户端 发送信息:send()

ssize_t send(int sockfd, const void *buf, size_t len, int flags)
  • 参数
    • buf: 存放需要被发送数据的缓冲区
    • len: 将要被发送数据的长度
    • flags: 一般设0, 其他数值定义如下:
      • MSG_OOB 传送的数据以out-of-band 送出.
      • MSG_DONTROUTE 取消路由表查询
      • MSG_DONTWAIT 设置为不可阻断运作
      • MSG_NOSIGNAL 此动作不愿被SIGPIPE 信号中断
  • 返回值:成功则返回实际传送出去的字符数, 失败返回-1.
  • 注意并不是send()把sockfd的发送缓冲中的数据传到连接的另一端的,而是协议传的,send()仅仅是把buf中的数据copy到sockfd的发送缓冲区的剩余空间里
  • 函数实现的过程细节
    1. send()先比较待发送数据的长度len和套接字s的发送缓冲的长度, 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR(-1);
    2. 如果len小于或者等于sockfd的发送缓冲区的长度,那么send()先检查协议是否正在发送sockfd的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送sockfd的发送缓冲中的数据或者sockfd的发送缓冲中没有数据,那么send()比较sockfd的发送缓冲区的剩余空间和len
    3. 如果len大于剩余空间大小,send()就一直等待协议把sockfd的发送缓冲中的数据发送完
    4. 如果len小于剩余 空间大小,send()就仅仅把buf中的数据copy到剩余空间里

2.3.5. 服务端/客户端 接收信息:recv()

ssize_t recv(int sockfd, void *buf, size_t len, int flags)
  • 参数与send()相同
  • 调用细节
    • recv()先等待sockfd的发送缓冲中的数据被协议传送完毕,如果协议在传送sockfd的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR(-1),
    • 如果sockfd的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv()先检查套接字sockfd的接收缓冲区,如果sockfd接收缓冲区中没有数据或者协议正在接收数据,那么recv()就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv()函数就把sockfd的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv()函数才能把sockfd的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的)

这里我是引用了这篇文章:Socket send函数和recv函数详解

2.4. UDP的通信

UDP是无连接的,在做好初始化和bind()之后,就可以通讯了

2.4.1. 服务端/客户端 发送信息:sendto()

int sendto(int sour_socket, const void *buf, int buflen, unsigned int flags, const struct sockaddr * dest_socket, int destlen)
  • 本函数适用于发送未建立连接的UDP数据包.
  • sour_socket 为已建好连线的socket.
  • buf 是将要发送的数据内容
  • buflen 是将要发送内容的长度
  • flags 一般设0, 详细描述请参考send().
  • dest_socket 用来指定欲传送的网络地址.
  • destlensockaddr_in的结果长度

2.4.2. 服务端/客户端 接收信息:recvfrom()

int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from,int *fromlen)
  • recv()用来接收远程主机经指定的socket传来的数据, 并把数据存到由参数buf指向的内存空间.
  • len 为可接收数据的最大长度.
  • flags 一般设0, 其他数值定义请参考recv().
  • from 用来指定欲传送的网络地址.
  • fromlensockaddr_in的结构长度

2.5. 关闭socket

最后,不要忘记关闭释放socket套接字。其实,这也有两个函数可以实现。

int close(int sockfd)    
int shutdown(int sockfd,int how)
  • close()是最常用的,sockfd是套接字描述符。
  • shutdown()how有三种方式:
    • SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。即该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。进程将不能对该套接字发出任何读操作。对TCP套接字该调用之后接受到的任何数据将被确认然后无声的丢弃掉。
    • SHUT_WR(1):关闭sockfd的写功能,此选项将不允许sockfd进行写操作,即进程不能在对此套接字发出写操作。
    • SHUT_RDWR(2):关闭sockfd的读写功能,相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。
  • shutdown()的效果是累计的,不可逆转的。既如果关闭了一个方向数据传输,那么这个方向将会被关闭直至完全被关闭或删除,而不能重新被打开。如果第一次调用了shutdown(0),第二次调用了shutdown(1),那么这时的效果就相当于shutdown(2),也就是双向关闭socket。
  • 二者区别
    • close()–关闭本进程的socket id,但链接还是开着的,用这个socket id的其它进程还能用这个链接,能读或写这个socket id。
    • shutdown()–破坏了socket 链接,读的时候可能侦探到EOF结束符,写的时候可能会收到一个SIGPIPE信号,这个信号可能直到socket buffer被填充了才收到,shutdown有一个关闭方式的参数,0 不能再读,1不能再写,2 读写都不能。

附上一篇很详细的讲述:Linux-socket的close和shutdown区别及应用场景



3. 简单的代码实现

这里附上TCP/UDP的简单通信实现。拉下来跑一跑,对照上面的函数介绍看,以加深印象。

3.1. TCP

3.1.1. server

#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
 
#define SERVER_PORT 5555
 
/*
 监听后,一直处于accept阻塞状态,
 直到有客户端连接,
 当客户端如数quit后,断开与客户端的连接
 */
 
int main()
{
	int serverSocket;
    //声明两个套接字sockaddr_in结构体变量,分别表示客户端和服务器
	struct sockaddr_in server_addr;
	struct sockaddr_in clientAddr;
	int addr_len = sizeof(clientAddr);
	int client;
	char buffer[200];
	int iDataNum;
    
	if((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket");
		return 1;
	}
 
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
	{
		perror("connect");
		return 1;
	}
    //设置服务器上的socket为监听状态
	if(listen(serverSocket, 5) < 0) 
	{
		perror("listen");
		return 1;
	}
 
	while(1)
	{
		printf("Listening on port: %d\n", SERVER_PORT);
		client = accept(serverSocket, (struct sockaddr*)&clientAddr, (socklen_t*)&addr_len);
		if(client < 0)
		{
			perror("accept");
			continue;
		}
		printf("\nrecv client data...n");
		printf("IP is %s\n", inet_ntoa(clientAddr.sin_addr));
		printf("Port is %d\n", htons(clientAddr.sin_port));
		while(1)
		{
			iDataNum = recv(client, buffer, 1024, 0);
			if(iDataNum < 0)
			{
				perror("recv");
				continue;
			}
			buffer[iDataNum] = '\0';
			if(strcmp(buffer, "quit") == 0) 
				//break						//其实应该break,因为server停止了和一个client的链接,还会等待其他的链接
				return 0;
			printf("data len is %d recv data is %s\n", iDataNum, buffer);
			send(client, buffer, iDataNum, 0);
		}
	}
	return 0;
}

3.1.2. client

#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
 
#define SERVER_PORT 5555
 
/*
 连接到服务器后,会不停循环,等待输入,
 输入quit后,断开与服务器的连接
 */
 
int main()
{
    //客户端只需要一个套接字文件描述符,用于和服务器通信
	int clientSocket;
    //描述服务器的socket
	struct sockaddr_in serverAddr;
	char sendbuf[200];
	char recvbuf[200];
	int iDataNum;
	if((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket");
		return 1;
	}
 
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(SERVER_PORT);
    //指定服务器端的ip,本地测试:127.0.0.1
    //inet_addr()函数,将点分十进制IP转换成网络字节序IP
	serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	if(connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
	{
		perror("connect");
		return 1;
	}
 
	printf("connect with destination host...\n");
 
	while(1)
	{
		printf("Input your world:>");
		scanf("%s", sendbuf);
		printf("\n");
 
		send(clientSocket, sendbuf, strlen(sendbuf), 0);
		if(strcmp(sendbuf, "quit") == 0)
			break;
		iDataNum = recv(clientSocket, recvbuf, 200, 0);
		recvbuf[iDataNum] = '\0';
		printf("recv data of my world is: %s\n", recvbuf);
	}
	close(clientSocket);
	return 0;
}

3.2. UDP

3.2.1. server

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

#define BUFFLEN 1024
#define SERVER_PORT 8888
int main(int argc, char *argv[])
{
    int server; /*服务器套接字文件描述符*/
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr; /*本地地址*/
    char buffer[BUFFLEN];
    int iDataNum = 0;
    socklen_t len = sizeof(client_addr);

    server = socket(AF_INET, SOCK_DGRAM, 0); /*建立UDP套接字*/

    /*初始化地址*/
    memset(&server_addr, 0, sizeof(server_addr));    /*清零*/
    server_addr.sin_family = AF_INET;                /*AF_INET协议族*/
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*任意本地地址*/
    server_addr.sin_port = htons(SERVER_PORT);       /*服务器端口*/

    /*将套接字文件描述符绑定到本地地址和端口*/
    bind(server, (struct sockaddr *)&server_addr, sizeof(server_addr));
    /*主处理过程*/

    printf("waiting...\n");
    while (1)
    {
        memset(buffer, 0, BUFFLEN); /*清零*/
        iDataNum = recvfrom(server, buffer, BUFFLEN, 0, (struct sockaddr *)&client_addr, &len);
        printf("IP is %s\n", inet_ntoa(client_addr.sin_addr));
        printf("Port is %d\n", htons(client_addr.sin_port));
        buffer[iDataNum] = '\0';
        printf("data len is %d recv data is %s\n", iDataNum, buffer);
        if (strcmp(buffer, "quit") == 0)
            break; //其实应该break到一个循环,因为server停止了和一个client的链接,还会等待其他的链接
        /*接收发送方数据*/
        sendto(server, buffer, strlen(buffer), 0, (struct sockaddr *)&client_addr, len);
    }

    close(server);

    return 0;
}

3.2.2. client

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
int main(int argc, char *argv[])
{
    int s;                     /*服务器套接字文件描述符*/
    struct sockaddr_in server; /*本地地址*/
    char sendbuf[BUFFLEN];
	char recvbuf[BUFFLEN];
    int iDataNum = 0;                 /*接收字符串长度*/
    socklen_t len = 0;         /*地址长度*/

    s = socket(AF_INET, SOCK_DGRAM, 0); /*建立UDP套接字*/

    /*初始化地址初始化*/
    memset(&server, 0, sizeof(server));         /*清零*/
    server.sin_family = AF_INET;                /*AF_INET协议族*/
    server.sin_addr.s_addr = htonl(INADDR_ANY); /*任意本地地址*/
    server.sin_port = htons(SERVER_PORT);       /*服务器端口*/


    while (1)
    {
        printf("Input your world:>");
        scanf("%s", sendbuf);
        printf("\n");
        sendto(s, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&server, sizeof(server));
        if (strcmp(sendbuf, "quit") == 0)
            break;
        len = sizeof(server);
        iDataNum = recvfrom(s, recvbuf, BUFFLEN, 0, (struct sockaddr *)&server, &len);
        recvbuf[iDataNum] = '\0';
        printf("recv data of my world is: %s\n", recvbuf);
    }
    close(s);
}

4. 进阶

4.1. setsockopt()

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)

此函数,用于任意类型、任意状态套接口的设置选项值。具体使用请自行百度。

这里附上一篇详细讲解,有兴趣可查看:setsockopt()函数功能介绍

4.2. getsockname()getpeername()

int getsockname(int sockfd,struct sockaddr* localaddr,socklen_t *addrlen);
int getpeername(int sockfd,struct sockaddr* peeraddr,socklen_t *addrlen);

这两个函数或者返回与某个套接字关联的本地协议地址(getsockname()),或者返回与某个套接字关联的外地协议地址即得到对方的地址(getpeername())

这里附上一篇详细讲解,有兴趣可查看:UNIX网络编程——getsockname和getpeername函数

4.3. select()

后续会讲到。

发布了19 篇原创文章 · 获赞 1 · 访问量 926

猜你喜欢

转载自blog.csdn.net/tjw316248269/article/details/104837025