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

时间:2022-02-10 11:01:51
为了实现服务器与客户机的通信,服务器和客户机都必须建立套接字。服务器与客户机的工作原理可以用下面的过程来描述。
(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连接。
客户机与服务器建立面向连接的套接字进行通信,请求与响应过程可用下图来表示。

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

绑定端口

绑定端口指的是将套接字与指定的端口相连。用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