Linux 网络socket总结

公共网站:

http://www.cs.dartmouth.edu/~campbell/cs50/socketprogramming.html

http://www.tenouk.com/Module42.html

转载博客:

https://www.cnblogs.com/rainbow1122/p/7852570.html

https://www.cnblogs.com/jiangzhaowei/p/8261174.html

https://segmentfault.com/a/1190000004570985

1 socket套接字:

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

文件描述符:linux系统中打开文件就会获得文件描述符,它是个很小的正整数。每个进程在PCBProcess Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。


网络字节序与主机字节序

主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-EndianLittle-Endian的定义如下:

a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

网络字节序4个字节的32bit值以下面的次序传输:首先是07bit,其次815bit,然后1623bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket

4)相同字节序的平台在进行网络通信时可以不进行字节序转换,但是跨平台进行网络数据通信时必须进行字节序转换。 
原因如下:网络协议规定接收到得第一个字节是高字节,存放到低地址,所以发送时会首先去低地址取数据的高字节。小端模式的多字节数据在存放时,低地址存放的是低字节,而被发送方网络协议函数发送时会首先去低地址取数据(想要取高字节,真正取得是低字节),接收方网络协议函数接收时会将接收到的第一个字节存放到低地址(想要接收高字节,真正接收的是低字节),所以最后双方都正确的收发了数据。而相同平台进行通信时,如果双方都进行转换最后虽然能够正确收发数据,但是所做的转换是没有意义的,造成资源的浪费。而不同平台进行通信时必须进行转换,不转换会造成错误的收发数据,字节序转换函数会根据当前平台的存储模式做出相应正确的转换,如果当前平台是大端,则直接返回不进行转换,如果当前平台是小端,会将接收到得网络字节序进行转换。 
htons() –
将一个16位数从主机字节顺序转换成网络字节顺序。返回值:htons()返回一个网络字节顺序的值 
ntohs() –
本函数将一个16位数由网络字节顺序转换为主机字节顺序。返回值:ntohs()返回一个以主机字节顺序表达的数

 

 

 select系统调用是用来让我们的程序监视多个文件句柄(filedescriptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。

select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组, 每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他 文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成, 当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一Socket或文件可读写。当readfds或writefds中映象的文件可读或可写或超时,本次select()  就结束返回。程序员利用系统提供的宏FD_ISSET(intfd, fd_set *fdset) 在select()结束时便可判断哪一文件可读或可写。这样我们编写的socket只需要在有东西可读入或写的时候才进行操作


高并发socket -- 通信要使用select和epoll(更高效)。

示例代码(转载):

#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#define BACKLOG 5     //完成三次握手但没有accept的队列的长度
#define CONCURRENT_MAX 8   //应用层同时可以处理的连接
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
int main(int argc, const char * argv[])
{
    char input_msg[BUFFER_SIZE];
    char recv_msg[BUFFER_SIZE];
    //本地地址
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    bzero(&(server_addr.sin_zero), 8);
    //创建socket
    int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(server_sock_fd == -1)
    {
    	perror("socket error");
    	return 1;
    }
    //绑定socket
    int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if(bind_result == -1)
    {
    	perror("bind error");
    	return 1;
    }
    //listen
    if(listen(server_sock_fd, BACKLOG) == -1)
    {
    	perror("listen error");
    	return 1;
    }
    //fd_set
    fd_set server_fd_set;
    int max_fd = -1;
    struct timeval tv;  //超时时间设置
    while(1)
    {
    	tv.tv_sec = 20;
    	tv.tv_usec = 0;
    	FD_ZERO(&server_fd_set);
    	FD_SET(STDIN_FILENO, &server_fd_set);
    	if(max_fd <STDIN_FILENO)
    	{
    		max_fd = STDIN_FILENO;
    	}
        //printf("STDIN_FILENO=%d\n", STDIN_FILENO);
	//服务器端socket
        FD_SET(server_sock_fd, &server_fd_set);
       // printf("server_sock_fd=%d\n", server_sock_fd);
        if(max_fd < server_sock_fd)
        {
        	max_fd = server_sock_fd;
        }
	//客户端连接
        for(int i =0; i < CONCURRENT_MAX; i++)
        {
        	//printf("client_fds[%d]=%d\n", i, client_fds[i]);
        	if(client_fds[i] != 0)
        	{
        		FD_SET(client_fds[i], &server_fd_set);
        		if(max_fd < client_fds[i])
        		{
        			max_fd = client_fds[i];
        		}
        	}
        }
        int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);
        if(ret < 0)
        {
        	perror("select 出错\n");
        	continue;
        }
        else if(ret == 0)
        {
        	printf("select 超时\n");
        	continue;
        }
        else
        {
        	//ret 为未状态发生变化的文件描述符的个数
        	if(FD_ISSET(STDIN_FILENO, &server_fd_set))
        	{
        		printf("发送消息:\n");
        		bzero(input_msg, BUFFER_SIZE);
        		fgets(input_msg, BUFFER_SIZE, stdin);
        		//输入“.quit"则退出服务器
        		if(strcmp(input_msg, QUIT_CMD) == 0)
        		{
        			exit(0);
        		}
        		for(int i = 0; i < CONCURRENT_MAX; i++)
        		{
        			if(client_fds[i] != 0)
        			{
        				printf("client_fds[%d]=%d\n", i, client_fds[i]);
        				send(client_fds[i], input_msg, BUFFER_SIZE, 0);
        			}
        		}
        	}
        	if(FD_ISSET(server_sock_fd, &server_fd_set))
        	{
        		//有新的连接请求
        		struct sockaddr_in client_address;
        		socklen_t address_len;
        		int client_sock_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
        		printf("new connection client_sock_fd = %d\n", client_sock_fd);
        		if(client_sock_fd > 0)
        		{
        			int index = -1;
        			for(int i = 0; i < CONCURRENT_MAX; i++)
        			{
        				if(client_fds[i] == 0)
        				{
        					index = i;
        					client_fds[i] = client_sock_fd;
        					break;
        				}
        			}
        			if(index >= 0)
        			{
        				printf("新客户端(%d)加入成功 %s:%d\n", index, inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
        			}
        			else
        			{
        				bzero(input_msg, BUFFER_SIZE);
        				strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");
        				send(client_sock_fd, input_msg, BUFFER_SIZE, 0);
        				printf("客户端连接数达到最大值,新客户端加入失败 %s:%d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
        			}
        		}
        	}
        	for(int i =0; i < CONCURRENT_MAX; i++)
        	{
        		if(client_fds[i] !=0)
        		{
        			if(FD_ISSET(client_fds[i], &server_fd_set))
        			{
        				//处理某个客户端过来的消息
        				bzero(recv_msg, BUFFER_SIZE);
        				long byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0);
        				if (byte_num > 0)
        				{
        					if(byte_num > BUFFER_SIZE)
        					{
        						byte_num = BUFFER_SIZE;
        					}
        					recv_msg[byte_num] = '\0';
        					printf("客户端(%d):%s\n", i, recv_msg);
        				}
        				else if(byte_num < 0)
        				{
        					printf("从客户端(%d)接受消息出错.\n", i);
        				}
        				else
        				{
        					FD_CLR(client_fds[i], &server_fd_set);
        					client_fds[i] = 0;
        					printf("客户端(%d)退出了\n", i);
        				}
        			}
        		}
        	}
        }
    }
    return 0;
}#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#define BUFFER_SIZE 1024

int main(int argc, const char * argv[])
{
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(11332);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    bzero(&(server_addr.sin_zero), 8);

    int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(server_sock_fd == -1)
    {
	perror("socket error");
	return 1;
    }
    char recv_msg[BUFFER_SIZE];
    char input_msg[BUFFER_SIZE];

    if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)
    {
	fd_set client_fd_set;
	struct timeval tv;

	while(1)
	{
		tv.tv_sec = 20;
		tv.tv_usec = 0;
	    FD_ZERO(&client_fd_set);
	    FD_SET(STDIN_FILENO, &client_fd_set);
	    FD_SET(server_sock_fd, &client_fd_set);

	   select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);
		if(FD_ISSET(STDIN_FILENO, &client_fd_set))
		{
		    bzero(input_msg, BUFFER_SIZE);
		    fgets(input_msg, BUFFER_SIZE, stdin);
		    if(send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1)
		    {
		    	perror("发送消息出错!\n");
		    }
		}
		if(FD_ISSET(server_sock_fd, &client_fd_set))
		{
		    bzero(recv_msg, BUFFER_SIZE);
		    long byte_num = recv(server_sock_fd, recv_msg, BUFFER_SIZE, 0);
		    if(byte_num > 0)
		    {
			if(byte_num > BUFFER_SIZE)
			{
			    byte_num = BUFFER_SIZE;
			}
			recv_msg[byte_num] = '\0';
			printf("服务器:%s\n", recv_msg);
		    }
		    else if(byte_num < 0)
		    {
			printf("接受消息出错!\n");
		    }
		    else
		    {
			printf("服务器端退出!\n");
			exit(0);
		    }
		}
	    }
	//}
    }
    return 0;
}


常用开源网络库libevent、libev、libuv.


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值