Linux_socket笔记
文章目录
引言
Socket接口是TCP/IP网络的API,Socket接口下定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序,也就是可以通过C语言编写与网络通信相关的函数。在网络通信中,通常使用C-S(客户端-服务端)模型,客户端主动要求链接到服务器端,服务端收到请求后,然后客户端和服务端双方便可以开始通信,一般服务端被动地接收客户端的信息,然后做出相应动作,如:回复客户端等。
TCP与UDP的区别
- TCP是面向连接,是可靠的字节流服务。客户端与服务器双方通信前必须要在之间建立TCP连接(三次握手),优点:保证数据的正确性。缺点:传输效率较低,占用资源高,易被攻击。使用场景:数据量不是特别大,且要求保证准确性。
- UDP面向数据报传输,比用TCP的协议程序简单但没有建立可靠连接,它只是把数据报发出去,不保证数据能到达目的地。优点:相较于TCP,传输速率快,也安全。缺点:数据传输不可靠,不稳定。使用场景:对通信质量要求不高,但要求快,如:播放视频,可以掉几帧画面数据,但不为了数据的准确性而阻塞在某一画面上。另外,UDP可以通过
setsockopt()
在协议层设置多播或组播
c/s模型(客户端/服务端)
客户端:主动与服务端建立通信。
服务端:被动等待客户端通信。
所以,要先建立运行服务端,再运行客户端。
soket下以TCP协议通信
服务端(server)
- 与客户端相同,用
socket()
获取socketfd。 - 与客户端相同,初始化
struct sockaddr_in tSocketServerAddr
。 bind()
把sockfd和&sockaddr_in绑定在一起,相当配置服务器的相关信息,如:iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
。- 通过
listen()
指定监听端口。如:iRet = listen(iSocketServer, 10);
,其中10
表示监听队列的长度。 - 通过
accept()
接受客户端连接请求,并返回客户端的socket_fd,该函数默认为阻塞状态,所以,若想并发地接受多个服务端请求,并处理多服务端的操作,需用到多线程或多路IO复用。 - 通过
read()/write()
或send()/recv()
开始读写操作,与客户端类似。注意:服务端发送数据是往accept()
获得的客户端的socket_fd发送,而接收数据则是从自己通过socket()的fd接收。
例子
以下代码为韦东山老师的例程:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/* socket
* bind
* listen
* accept
* send/recv
*/
#define SERVER_PORT 8888
#define BACKLOG 10
int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRet;
int iAddrLen;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iClientNum = -1;
signal(SIGCHLD,SIG_IGN);
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\n");
return -1;
}
iRet = listen(iSocketServer, BACKLOG);
if (-1 == iRet)
{
printf("listen error!\n");
return -1;
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (-1 != iSocketClient)
{
iClientNum++;
printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork())
{
/* 子进程的源码 */
while (1)
{
/* 接收客户端发来的数据并显示出来 */
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient);
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
}
}
}
}
}
close(iSocketServer);
return 0;
}
客户端(client)
-
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
设定通信协议族为AF_INET
;设socket类型为数据流(该类型应与参数3通信协议类型匹配):SOCK_STREAM
,该类型匹配的通信协议必须为tcp;设定通信协议:0
,表示根据参数2自动匹配。 -
通过初始化
struct sockaddr_in
,设定要连接的服务端的相关信息。struct sockaddr_in tSocketServerAddr; tSocketServerAddr.sin_family = AF_INET; tSocketServerAddr.sin_port = htons(SERVER_PORT); tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /*或 tSocketServerAddr.sin_addr.s_addr = inet_addr("192.168.138.10"); */ memset(tSocketServerAddr.sin_zero, 0, 8);
其中
sin_family
表示目标服务器的协议族;sin_port
表示要连接到目标服务器的端口,htons(SERVER_PORT)
是将整型变量SERVER_PORT
从主机字节顺序转变成网络字节顺序(host to net,s表示两字节,l表示四字节);sin_addr.s_addr
表示目标服务器的IP地址,其中,INADDR_ANY
表示本机地址,inet_addr("192.168.138.10")
表示把表示IP地址的字符串转换为sin_addr.s_addr
能正确接受的整数值(也可以用inet_pton()
)。反之,将 IP转换成“.”点隔的字符串格式用inet_ntoa (struct in_addr)
。 -
通过
conect(iSocketClient, (const struct sockaddr* )&tSocketServerAddr, sizeof(struct sockaddr))
函数连接服务器,连接失败返回-1。 -
连接成功后,通过
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf),0);
或write(iSocketClient, ucSendBuf, strlen(ucSendBuf))
发送数据,若发送成功,则返回实际发送的长度。通过recv(iSocketClient, ucSendBuf, strlen(ucSendBuf),0);
或read(iSocketClient, ucSendBuf, strlen(ucSendBuf));
接受数据(阻塞)。
例子
以下代码为韦东山老师的例程:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/* socket
* connect
* send/recv
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
unsigned char ucSendBuf[1000];
int iSendLen;
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n");
return -1;
}
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
return 0;
}
soket下以UDP协议通信
服务端(server)
-
通过
iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
创建socket_fd。 -
初始化
struct sockaddr_in tSocketServerAddr
,与tcp服务端中的相同。 -
bind()
把sockfd和&sockaddr_in绑定在一起,相当配置服务器的相关信息,与tcp服务端中的相同。 -
通过
sendto()/recvfrom()
开始读写操作。注意:由于UDP无需通过
accept()
建立连接,也就无法获取客户端的fd,所以,通过read()\recv()
可以接受到信息,但无法知道来自那个客户端;通过send()/write()
也无法发送给客户端,因为没有客户端的fd;当要求服务器既能读也能写时,用sendto()/recvfrom()
,通过加入的参数(struct sockaddr *)&tSocketClientAddr
获取服务端的IP地址。
相对于TCP的不同:socket中参数2为SOCK_DGRAM
,无需listen()
和accept()
。
例子
以下代码为韦东山老师的例程:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/* socket
* bind
* sendto/recvfrom
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRet;
int iAddrLen;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iClientNum = -1;
iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\n");
return -1;
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
}
}
close(iSocketServer);
return 0;
}
客户端(client)
- 通过
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
创建socket_fd。 - 初始化
struct sockaddr_in tSocketServerAddr;
,表示要连接的服务器的相关信息。 - 通过
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
连接服务器。 - 通过
sendto()/recvfrom()
开始读写操作。
例子
以下代码为韦东山老师的例程:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/* socket
* connect
* send/recv
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
unsigned char ucSendBuf[1000];
int iSendLen;
int iAddrLen;
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);
#if 0
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n");
return -1;
}
#endif
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
#if 0
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
#else
iAddrLen = sizeof(struct sockaddr);
iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0,
(const struct sockaddr *)&tSocketServerAddr, iAddrLen);
#endif
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
return 0;
}
注意
-
客户端一般不需用到
bind()
函数,因为它一般不需特别设定自己的IP、端口,可由系统随机分配空闲的。而服务端则需要bind()
,因为它必须要确定自己的IP、端口不变,以便服务端能找到并连接。 -
在上述所有程序中,
bind()
中所绑定的结构体类型struct sockaddr_in
,会根据不同的协议族有所不同,man 2 bind
中出现For AF_INET see ip(7)
,所以通过man 7 ip
,可以在到Address Format
下找到所要定义的结构体为struct sockaddr_in
。 -
在终端中,通过命令
netstat -anu
查看udp通信的网络状态,netstat -ant
则是查看tcp的。 -
网络传输的内容不能传递指针,因为客户端和服务端可能运行在不同的设备上,指针所指向的内容也不同。所以一般传输格式为首地址加数据长度,一点一点传输,指导所有数据传完。
-
在终端调试自己写的服务端程序正常时可以使用
nc <服务端ip地址> <服务端端口号>
命令来向服务端发送一个请求。(telnet
命令也有类似的效果)