Linux_socket笔记

Linux_socket笔记

引言

Socket接口是TCP/IP网络的API,Socket接口下定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序,也就是可以通过C语言编写与网络通信相关的函数。在网络通信中,通常使用C-S(客户端-服务端)模型,客户端主动要求链接到服务器端,服务端收到请求后,然后客户端和服务端双方便可以开始通信,一般服务端被动地接收客户端的信息,然后做出相应动作,如:回复客户端等。

TCP与UDP的区别

  1. TCP是面向连接,是可靠的字节流服务。客户端与服务器双方通信前必须要在之间建立TCP连接(三次握手),优点:保证数据的正确性。缺点:传输效率较低,占用资源高,易被攻击。使用场景:数据量不是特别大,且要求保证准确性。
  2. UDP面向数据报传输,比用TCP的协议程序简单但没有建立可靠连接,它只是把数据报发出去,不保证数据能到达目的地。优点:相较于TCP,传输速率快,也安全。缺点:数据传输不可靠,不稳定。使用场景:对通信质量要求不高,但要求快,如:播放视频,可以掉几帧画面数据,但不为了数据的准确性而阻塞在某一画面上。另外,UDP可以通过setsockopt()在协议层设置多播组播

c/s模型(客户端/服务端)

客户端:主动与服务端建立通信。

服务端:被动等待客户端通信。

所以,要先建立运行服务端,再运行客户端。

soket下以TCP协议通信

服务端(server)

  1. 与客户端相同,用socket()获取socketfd
  2. 与客户端相同,初始化struct sockaddr_in tSocketServerAddr
  3. bind()把sockfd和&sockaddr_in绑定在一起,相当配置服务器的相关信息,如:iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
  4. 通过listen()指定监听端口。如:iRet = listen(iSocketServer, 10);,其中10表示监听队列的长度。
  5. 通过accept()接受客户端连接请求,并返回客户端的socket_fd,该函数默认为阻塞状态,所以,若想并发地接受多个服务端请求,并处理多服务端的操作,需用到多线程或多路IO复用。
  6. 通过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)

  1. iSocketClient = socket(AF_INET, SOCK_STREAM, 0);设定通信协议族为AF_INET;设socket类型为数据流(该类型应与参数3通信协议类型匹配):SOCK_STREAM,该类型匹配的通信协议必须为tcp;设定通信协议:0,表示根据参数2自动匹配。

  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)

  3. 通过conect(iSocketClient, (const struct sockaddr* )&tSocketServerAddr, sizeof(struct sockaddr))函数连接服务器,连接失败返回-1。

  4. 连接成功后,通过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)

  1. 通过iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);创建socket_fd。

  2. 初始化struct sockaddr_in tSocketServerAddr,与tcp服务端中的相同。

  3. bind()把sockfd和&sockaddr_in绑定在一起,相当配置服务器的相关信息,与tcp服务端中的相同。

  4. 通过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)

  1. 通过iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);创建socket_fd。
  2. 初始化struct sockaddr_in tSocketServerAddr;,表示要连接的服务器的相关信息。
  3. 通过iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));连接服务器。
  4. 通过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命令也有类似的效果)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值