网络编程知识点

网络编程

1、网络中进程之间如何通信?

本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:消息传递(管道、FIFO、消息队列)
同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
共享内存(匿名的和具名的)
远程过程调用(Solaris门和Sun RPC)

2、三次握手和四次挥手

三次握手

客户端向服务器发送一个SYN J
服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
客户端再想服务器发一个确认ACK K+1
只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
网络编程知识点_第1张图片
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

四次挥手
上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:
网络编程知识点_第2张图片
图2、socket中发送的TCP四次握手
图示过程如下:
某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
接收到这个FIN的源发送端TCP对它进行确认。
这样每个方向上都有一个FIN和ACK。

3、函数

(1) Socket函数

#include
#include
int socket(int domain, int type, int protocol);

Socket函数详细介绍

(2)bind函数

bind函数赋予socket “协议, ip地址, 端口号” 属性,服务器在启动时捆绑它众所周知的端口。
对于服务器,则限定了该套接字只能接收那些目的地为这个IP地址的客户连接。
TCP客户端通常不把IP地址捆绑到它的套接字上。当连接套接字时,内核将根据所用的外出网络接口来选择源IP地址,而所用外出接口则取决于到达服务器所需的路径。

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include           /* See NOTES */
       #include 

       int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

第一个参数为socket文件描述符;
第二个参数,传入sockaddr 类型结构体,配置socket的网络地址和端口;
第三个参数为长度;

一般用法:

bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr=htonl(INADDR_ANY);
s_add.sin_port=htons(portnum);
if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr))){
		printf("bind fail !\r\n");
		return -1;
	}

(3)accept函数

accept()是在一个套接口接受的一个连接。accept()是c语言中网络编程的重要的函数,本函数从s的等待连接队列中抽取第一个连接,创建一个与s同类的新的套接口并返回句柄。

(4)listen函数

1.作用
listen函数的作用是把一个未连接的套接字转换为被动套接字,指示内核应该接受指向该套接字的连接请求。使套接字从CLOSED状态转换到LISTEN状态。

#include 
#include 
int listen(int sockfd, int backlog);
//返回0成功,-1失败;

(5)connect和accept函数

服务器调用accept函数接收客户端连接,一直阻塞到有连接请求,accept返回标识符;
客户端用connect请求连接;

NAME
      connect - initiate a connection on a socket

SYNOPSIS
      #include 
      #include 
//成功返回0,失败返回-1;
      int connect(int sockfd, const struct sockaddr *addr,
                  socklen_t addrlen);

NAME
       accept - accept a connection on a socket

SYNOPSIS
       #include           /* See NOTES */
       #include 
//成功返回连接标识,失败返回-1;
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

(4)setsockopt()函数功能介绍

setsockopt的用法

1.closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;

setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));

  1. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:
    BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000;//1秒

//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:

// 接收缓冲区
int nRecvBuf=32*1024;
//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;
//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));


  1. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));

6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):

int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:

BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的作用,在阻塞的函数调用中作用不大)

BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));

9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体应用的要求(即让没发完的数据发送出去后在关闭socket)?

struct linger {
u_short l_onoff;
u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
// 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;
m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

4、实例

根据介绍的知识点,写一个服务器和客户端程序,实现的功能是,能够相互通讯.
根据要求,首先考虑,先将服务器与客户端建立连接;
服务器在建立连接后,新开一个进程,用来接收信息;
当前进程用来给客户端发送信息;

//server
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include
typedef struct sockaddr_in Sockaddr_in;
typedef struct sockaddr Sockaddr;
int main()
{
	int nfp;
	int sock_conn;
	int bytes;
	
	char sendbuf[100]={0};
	char recvbuf[100]={0};
	
	pid_t pid;
	
	unsigned short portnum=0x8888;
	Sockaddr_in s_add,c_add;
	//socket
	nfp=socket(AF_INET,SOCK_STREAM,0);
	
	bzero(&s_add,sizeof(Sockaddr_in));
	s_add.sin_family=AF_INET;
	s_add.sin_addr.s_addr=htonl(INADDR_ANY);
	s_add.sin_port=htons(portnum);
	
	//bind
	bind(nfp,(Sockaddr *)(&s_add),sizeof(struct sockaddr));
	
	//listen
	listen(nfp,5);
	
	int size=sizeof(Sockaddr_in);
	//accept 连接上
	bzero(&c_add,sizeof(Sockaddr_in));
	sock_conn=accept(nfp,(Sockaddr *)(&c_add),&size);
	printf("addr:%d\n",inet_ntoa(c_add.sin_addr.s_addr));
	sprintf(sendbuf,"welcome to my server");
	send(sock_conn,sendbuf,strlen(sendbuf)+1,0);
	
	pid=fork();
	
	if(pid==-1)
	{
		printf("fork failed\n");
		return -1;
	}
	//当前进程,无法显示;
	else if( pid )
	{
		while( (bytes=recv(sock_conn,recvbuf,100,0))>0)
		{
			//只改了打印的字体;
			printf("recv date is : %s\n",recvbuf);
			usleep(5);
		}
		close(sock_conn);
	}
	else
	{
		//
		while(1)
		{
			//发送消息;
			gets(sendbuf);
			send(sock_conn,sendbuf,strlen(sendbuf)+1,0);
			usleep(5);
		}
		
	}
	
	close(nfp);
	return 0;
	
}
//client
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include

int main(int argc,char *argv[])
{
	int spf,sockconn;
	unsigned short portnum=0x8888;
	struct sockaddr_in s_add;
	pid_t pid;
	int bytes;
	
	char sendbuf[100]={0};
	char recvbuf[100]={0};
		
	spf=socket(AF_INET,SOCK_STREAM,0);

	//配置要链接的服务器参数;
	bzero(&s_add,sizeof(struct sockaddr_in));
	s_add.sin_family=AF_INET;
	s_add.sin_addr.s_addr=inet_addr(argv[1]);
	s_add.sin_port=htons(portnum);
	
	connect(spf,(struct sockaddr *)(&s_add),sizeof(struct sockaddr));
	
	pid=fork();
	if(pid==-1)
	{
		printf("fork failed\n");
		return -1;
	}
	//当前进程显示接收;
	else if(pid)
	{
		while((bytes=recv(spf,recvbuf,100,0))>0)
		{
			printf("recv date:%s\n",recvbuf);
			usleep(5);
		}
	}
	//开启新进程捕捉输出;
	else
	{
		while(1)
		{
			gets(sendbuf);
			send(spf,sendbuf,strlen(sendbuf)+1,0);
			usleep(5);
		}
	}
	close(spf);
	return 0;
	
	
	
}

你可能感兴趣的:(Linux嵌入式学习之路)