您的位置:首页 > 理论基础 > 计算机网络

C语言网络编程(三)建立套接字通讯TCP

2015-11-10 22:21 411 查看
为了实现服务器与客户机的通信,服务器和客户机都必须建立套接字。服务器与客户机的工作原理可以用下面的过程来描述。

(1)服务器先用socket函数来建立一个套接字,用这个套接字完成通信的监听。

(2)用bind函数来绑定一个端口号和IP地址。因为本地计算机可能有多个网卡和IP,每一个IP有多个端口。需要指定一个IP和端口进行监听。

(3)服务器调用listen函数,使服务器的这个端口和IP处于监听状态,等待客户机的连接。

(4)客户机用socket函数建立一个套接字,设定远程IP和端口。

(5)客户机调用connect函数连接远程计算机指定的端口。

(6)服务器用accept函数来接受远程计算机的连接,建立起与客户机之间的通信。

(7)建立连接以后,客户机用write函数向socket中写入数据。也可以用read函数读取服务器发送来的数据。

(8)服务器用read函数读取客户机发送来的数据,也可以用write函数来发送数据。

(9)完成通信以后,用close函数关闭socket连接。

客户机与服务器建立面向连接的套接字进行通信,请求与响应过程可用下图来表示。


 

绑定端口

绑定端口指的是将套接字与指定的端口相连。用socket函数建立起一个套接字以后,需要用bind函数在这个套接字上面绑定一个端口。

提示:只有套接字建立后才能够执行端口绑定操作。 

绑定端口函数bind:函数bind可以将一个端口绑定到一个已经建立的socket上,这个函数的使用方法如下所示。
int bind(int sockfd,struct sockaddr * my_addr,int addrlen);

参数列表中,sockfd是已经建立的socket编号。sockaddr是一个指向sockaddr结构体类型的指针。sockaddr的定义方法如下所示。
struct sockaddr
{
  unsigned short int sa_family;
  char sa_data[14];
};

这个结构体的成员含义如下所示。

sa_family:为调用socket()时的domain参数,即AF_xxxx值。

sa_data:最多使用14个字符长度,含有IP地址与端口的信息。

如果建立socket时使用的是AF_INET参数,则sockaddr结构体的定义方法如下所示。
struct sockaddr_in
{
  unsigned short int sin_family;
  uint16_t sin_port;
  struct in_addr sin_addr;
  unsigned char sin_zero[8];
};

结构体的成员addr也是一个结构体,定义方式如下所示。
struct in_addr
{
  uint32_t s_addr;
};

在这些结构体中,成员变量的作用与含义如下所示。

sin_family:即为sa_family,为调用socket()时的domain参数。

sin_port:使用的端口号。

sin_addr.s_addr:IP 地址。

sin_zero:未使用的字段,填充为0。

参数addrlen是my_addr的长度,可以用sizeof函数来取得。函数可以把指定的IP与端口绑定到已经建立的socket上面。

如果绑定成功,则返回0。失败则返回-1。函数可能发生下面的错误,可以用error捕获发生的错误。

EBADF:参数sockfd不是一个合法的socket。

EACCESS:权限不足。

ENOTSOCK:参数sockfd是文件描述词,而不是socket。

监听

所谓监听,指的是socket的端口处于等待状态,如果有客户端有连接请求,这个端口会接受这个连接。连接指的是客户端向服务端发送一个通信申请,服务端会响应这个请求。本节将讲述socket的监听与连接操作。

等待监听函数listen:服务器必需等待客户端的连接请,listen函数用于实现监听等待功能。这个函数的使用方法如下所示。
int listen(int s,int backlog);
在参数列表中,s是已经建立的socket。backlog是能同时处理的最大连接请求。如果超过这个数目,客户端将会接收到ECONNREFUSED拒绝连接的错误。
注意:listen并未真正的接受连接,只是设置socket的状态为listen模式。真正接受客户端连接的是accept函数。通常情况下,listen函数会在socket、bind函数之后调用,然后才会调用accept函数。

listen函数只适用SOCK_STREAM或SOCK_SEQPACKET的socket类型。如果socket为AF_INET则参数backlog最大值可设至128,即最多可以同时接受128个客户端的请求。
如果调用成功,则函数的返回值为0,失败返回-1。函数可能发生如下所示的错误,可以用errno来捕获发生的错误。

EBADF:参数sockfd不是一个合法的socket。

EACCESS:权限不足。

EOPNOTSUPP:指定的socket不支持listen模式。

接受连接

接受连接函数accept:服务器处于监听状态时,如果获得客户机的请求,会将这个请求放在等待队列中。当系统空闲时,将处理客户机的连接请求。接受连接请求的函数是accept,这个函数的使用方法如下所示。
int accept(int s,struct sockaddr * addr,int * addrlen);

在参数列表中,s是处于监听状态的socket。addr是一个sockaddr结构体类型的指针,系统会把远程主机的这些信息保存到这个结构体指针上。addrlen是sockaddr的内存长度,可以用sizeof函数来取得。

注意:当accept函数接受一个连接时,会返回一个新的socket编号。以后的数据传输与读取就是通过这个新的socket编号来处理。原来参数中的socket可以继续使用。接受连接以后,远程主机的地址和端口信息将会保存到addr所指的结构体内。如果处理失败,返回值为-1。

函数可能产生下面的错误,可以用error来捕获发生的错误。

EBADF:参数s不是一个合法的socket代码。

EFAULT:参数addr指针指向无法存取的内存空间。

ENOTSOCK 参数s为一文件描述词,而不是一个socket。

EOPNOTSUPP:指定的socket不是SOCK_STREAM。

EPERM:防火墙拒绝这一个连接。

ENOBUFS:系统的缓冲内存不足。

ENOMEM:核心内存不足。

接受信息

建立套接字并完成网络连接以后,可以把信息传送到远程主机上,这个过程就是信息的发送。远程主机发送来的信息,本地主机需要进行接收处理。本节将讲述这种面向连接的套接字信息发送与接收操作。

数据接收函数recv:函数recv可以接收远程主机发送来的数据,并把这些数据保存到一个数组中。该函数的使用方法如下所示。
int recv(int s,void *buf,int len,unsigned int flags);

在参数列表中,s是已经建立的socket。buf是一个指针,指向一个数组。接收到的数据会保存到这个数组上。len是数组的长度,可以用sizeof函数来取得。flags一般设置为0,其他可能的赋值与含义如下所示。

MSG_OOB:接收以out-of-band送出的数据。

MSG_PEEK:返回来的数据并不会在系统内删除,如果再调用recv时会返回相同的数据内容。

MSG_WAITALL:强迫接收到len大小的数据后才能返回,除非有错误或信号产生。

MSG_NOSIGNAL:此操作被SIGPIPE信号中断。

recv函数如果接收到数据,会把这些数据保存在buf指针指向的内存中,然后返回接收到字符的个数。如果发生错误则会返回-1。函数可能发生下面这些错误,可以用errno来捕获错误。

EBADF:参数s不是一个合法的socket。

EFAULT:参数中的指针指向了无法读取的内存空间。

ENOTSOCK:参数s是文件描述词,而不是一个socket。

EINTR:被信号所中断。

EAGAIN:此动作会阻断进程,但参数s的socket不可阻断。

ENOBUFS:系统的缓冲内存不足。

ENOMEM:核心内存不足

EINVAL:参数不正确。

发送信息

信息发送函数send:用connect函数连接到远程计算机以后,可以用send函数将信息发送到对方的计算机。这个函数的使用方法如下所示。
int send(int s,const void * msg,int len,unsigned int flags);

在参数列表中,s是已经建立的socket。msg是需要发送数据的指针。len是需要发送数据的长度。这个长度可以用sizeof函数来取得。参数flags一般设置为0,可能的赋值与含义如下所示。

MSG_OOB:传送的数据以out-of-band的方式送出。

MSG_DONTROUTE:取消路由表查询。

MSG_DONTWAIT:设置为不可阻断传输。

MSG_NOSIGNAL:此传输不可被SIGPIPE 信号中断。。

如果数据成功,函数会返回已经传送的字符个数。否则会返回-1。函数可能发生下面这些错误,可以用errno来捕获函数的错误。

EBADF:参数s不是一个正确的socket。

EFAULT:参数中的指针指向了不可读取的内存空间。

ENOTSOCK:参数s是一个文件,而不是一个socket。

EINTR:被信号所中断。

EAGAIN:此操作会中断进程,但socket不允许中断。

ENOBUFS:系统的缓冲内存不足。

ENOMEM:核心内存不足。

EINVAL:传给系统调用的参数不正确。

DEMO:
static void
tcp_server_example()
{
int sockfd,rs,accept_sockfd,addr_len;
char buff[SERVER_MAX_BUFF];
char *msg="hello world!\n";
addr_len=sizeof(struct sockaddr);
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
return ;
}
struct sockaddr_in addr_in;
addr_in.sin_family=AF_INET;
addr_in.sin_port=htons(SERVER_PORT);//网络使用的整数与程序使用的不同,需要转换
addr_in.sin_addr.s_addr=htonl(INADDR_ANY);
bzero(&(addr_in.sin_zero),8);
rs=bind(sockfd,(struct sockaddr*)&addr_in,sizeof(addr_in));
if(rs<0)
{
printf("bind error!\n");
return;
}
rs=listen(sockfd,SERVER_MAX_CONECTION);
if(rs<0)
{
printf("listen error!\n");
return;
}
accept_sockfd=accept(sockfd,(struct sockaddr*)&addr_in,&addr_len);
while(1)
{
recv(accept_sockfd,buff,SERVER_MAX_BUFF,0);
printf("recv data:%s\n",buff);
send(accept_sockfd,msg,strlen(msg),0);
}
return ;
}

客户端连接 

请求连接函数connet:所谓请求连接,指的是客户机需要向服务器发送信息时,需要发送一个连接请求。connect函数可以完成这项功能,这个函数的使用方法如下所示。
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);

在参数列表中,sockfd是已经建立的socket。serv_addr是一个结构体指针,指向一个sockaddr结构体。这个结构体存储着远程服务器的IP与端口信息。addrlen是sockaddr结构体的内存长度,可以用sizeof函数来获取。sockaddr结构体的定义见前面小节中的bind函数所述。

函数会将本地的socket连接到serv_addr所指定的服务器IP与端口。如果连接成功,返回值为0,连接失败则返回-1。函数可能发生下面的错误,可以用error来捕获发生的错误。

EBADF:参数sockfd 不是一个合法的socket。

EFAULT:参数serv_addr指针指向了一个无法读取的内存空间。

ENOTSOCK:参数sockfd是文件描述词,而不是一个正常的socket。

EISCONN:参数sockfd的socket已经处于连接状态。

ECONNREFUSED:连接要求被服务器拒绝。

ETIMEDOUT:需要的连接操作超过限定时间仍未得到响应。

ENETUNREACH:无法传送数据包至指定的主机。

EAFNOSUPPORT:sockaddr结构的sa_family不正确。

EALREADY:socket不能阻断,但是以前的连接操作还未完成。

DEMO:

static void
tcp_client_example()
{
int sockfd,val,len,rs=-1,fromlen;
char buff[5000];
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
return;
}
//发送TCP请求
struct sockaddr_in addr_in;
addr_in.sin_family=AF_INET;
addr_in.sin_port=htons(80);//网络使用的整数与程序使用的不同,需要转换
addr_in.sin_addr.s_addr=inet_addr("1.1.1.1");
bzero(&(addr_in.sin_zero),8);
char *msg="GET /play.php HTTP/1.1\r\n\
Host: xxx.xxx.com\r\n\
Connection: keep-alive\r\n\
Cache-Control: max-age=0\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n\
Upgrade-Insecure-Requests: 1\r\n\
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36\r\n\
Accept-Language: zh-CN,zh;q=0.8\r\n\
Cookie: \r\n\r\n";
rs=connect(sockfd,(struct sockaddr *)&addr_in,sizeof(struct sockaddr));
if(rs<0)
{
printf("connect error!\n");
return ;
}
rs=send(sockfd,msg,strlen(msg),0);
printf("send %d bytes\n",rs);
rs=recv(sockfd,buff,sizeof(buff),0);
printf("recv %d bytes\n",rs);
printf("recv data:\n%s",buff);
return ;
}

其他技巧参考博客:
http://blog.csdn.net/maopig/article/details/17193021
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: