文章目录
- 1. 关键的结构体
- 2. 建立连接并且通讯
- 2.1. 创建套接字: `socket()`
- 2.2. 绑定端口/IP: `bind()`
- 2.3. TCP的通信
- 2.3.1. 进入监听状态: `listen()`
- 2.3.2. 客户端尝试与服务端建立连接:`connect()`
- 2.3.3. 服务端接受客户端连接请求:`accept()`
- 2.3.4. 服务端/客户端 发送信息:`send()`
- 2.3.5. 服务端/客户端 接收信息:`recv()`
- 2.4. UDP的通信
- 2.5. 关闭socket
- 3. 简单的代码实现
- 4. 进阶
关于 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>
中定义,该结构体把port
和addr
分开储存在两个变量中,这种方式解决了sockaddr
的缺陷。
1.3. 说明
sin_family
是地址族(Address Family),其格式位AF_XXXX
,对于socket编程,全部使用AF_INET
,这代表了TCP\UDP
。sin_port
和sin_addr(s_addr)
都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。sin_zero
虽然不怎么使用,但也要明白是干嘛的。其作用是为了使sockaddr
和sockaddr_in
两个数据结构大小保持相同而保留的空字节。sockaddr_in
和sockaddr
是并列的结构,指向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()
函数说明
初始化的时候,用到了这两个函数htons
和htonl
,那么本节就说下这两个以及相关的函数。
函数 | 说明 |
---|---|
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位数从网络字节顺序转换为主机字, 返回一个以主机字节顺序表达的数 |
我在进行初始化,自然要转地址位为网络地址,所以使用htons
和htonl
。而正如上面所说,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:100
和61.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_STREAM
、SOCK_DGRAM
、SOCK_RAW
、SOCK_PACKET
、SOCK_SEQPACKET
等等。 - protocol:顾名思义,就是指定协议。套接口所用的协议。如调用者不想指定,可用0(一般都用0)。常用的协议有,
IPPROTO_TCP
、IPPROTO_UDP
、IPPROTO_STCP
、IPPROTO_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
中的域进行设置之后才能进行绑定,这样进行绑定后才能将套接字文件描述符与地址等接合在一起。 - addrlen是
my_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()
是不需要的,虽然可以用,但真的没用。 -
总结一下:
- 需要在建连前就知道端口的话,需要 bind 。
- 需要通过指定的端口来通讯的话,需要 bind。
- 无论对UDP还是TCP,服务端是需要预先指定端口进行通讯的,而且对于连接比较复杂的情况,如果不预先指定端口/IP并且绑定,那么后续通讯就会出问题,所以服务端一定需要
bind()
。 - 对于客户端,使用
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
:套接字建立连接的主机地址和端口号。addrlen
:serv_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的发送缓冲区的剩余空间里 - 函数实现的过程细节
send()
先比较待发送数据的长度len和套接字s的发送缓冲的长度, 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR(-1);- 如果len小于或者等于sockfd的发送缓冲区的长度,那么
send()
先检查协议是否正在发送sockfd的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送sockfd的发送缓冲中的数据或者sockfd的发送缓冲中没有数据,那么send()
就比较sockfd的发送缓冲区的剩余空间和len - 如果len大于剩余空间大小,
send()
就一直等待协议把sockfd的发送缓冲中的数据发送完 - 如果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 用来指定欲传送的网络地址.
- destlen 为
sockaddr_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 用来指定欲传送的网络地址.
- fromlen 为
sockaddr_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()
后续会讲到。