目录
TCP介绍
TCP:传输控制协议,提供面向连接的,一对一的可靠数据传输协议。
TCP特点:能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。
TCP适用情况:
- 适合于对传输质量要求比较高,以及传输大量数据的通信。
- 在需要可靠数据传输的场合,通常适用TCP协议。
- MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议。
C/S模型框架
Server端
1.创建套接字
int socket(int domain, int type, int protocol);
该套接字是后面用来监听客户端请求的套接字。
2.绑定IP地址和端口号
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
该函数在填写addr参数时,要注意,我们先填写struct sockaddr_in这个结构体,再传递参数时,要将sockaddr_in类型的指针强转成sockaddr类型的指针。
注意:网络字节序和本地字节序的切换。
端口:
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
ip地址:
in_addr_t inet_addr(const char *cp);
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
3.使用该套接字监听连接请求
int listen(int sockfd, int backlog);
socket创建的套接字是一个主动套接字,也就是说是主动连接别人的一个套接字。listen函数将一个未连接的套接字转换成了一个被动套接字,等待连接。调用listen使套接字从closed状态转换成了listen状态。
4.当请求来到时,调用accept函数复制该套接字处理请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
一般我们将函数第一个参数称为监听套接字,将函数返回的套接字称为已连接套接字。一个服务器只有一个监听套接字,监听客户端的连接请求。服务器内核为每一个客户端的TCP连接维护1个已连接套接字,用它实现数据双向通信。
5.数据通信
利用accept返回的套接字,进行数据的通信。也就是IO操作了。
client端
1.创建套接字
同TCP
该套接字是用来向服务器发送请求的套接字。
2.使用创建好的套接字向服务端发送连接请求
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该函数第二个参数是一个结构体,该结构体中保存着发送连接请求对象的ip地址和端口号。
3.利用套接字进行数据的通信
server端简单示例代码
int main(int argc, const char *argv[])
{
int fd = -1;
struct sockaddr_in sin;
int ret ;
/*1.创建socket套接子*/
if((fd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket");
exit(1);
}
/*2.bind绑定,绑定之前填充sockaddr_in这个结构体*/
/*2.1 sin_zero 后八位要全部置零*/
bzero(&sin,sizeof(sin));
/*2.2 域指定IPV4编程*/
sin.sin_family = AF_INET;
/*2.3 填写端口号*/
sin.sin_port = htons(5001);
/*2.4 将网络地址填写到sin.sin_addr.s_addr中*/
sin.sin_addr.s_addr = inet_addr("192.168.1.3"); /*inet_addr函数只适用于IPV4*/
#if 0
inet_pton(AF_INET,"127.0.0.1",(void *)&sin.sin_addr.s_addr);/*inet_pton可以使用IPV6*/
#endif
#if 0
sin.sin_addr.s_addr = htonl(INADDR_ANY);
/*只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。*/
#endif
/*2.5 最后进行绑定*/
if(bind(fd,(struct sockaddr*)&sin,sizeof(sin)) < 0)
{
perror("bind");
exit(1);
}
/*3.listen 把主动套接子变成被动套接子*/
if(listen(fd,5) < 0)
{
perror("listen");
exit(1);
}
/*4.阻塞等待客户端链接请求*/
int newfd = -1;
newfd = accept(fd,NULL,NULL); /*两个NULL 不关心客户端的ip和端口号,所以就NULL*/
if(newfd < 0)
{
perror("accept");
exit(1);
}
/*5.读写*/
ret = -1;
char buf[255];
while(1)
{
bzero(buf,255);
do
{
ret = read(newfd,buf,255 - 1);
}while(ret < 0 && errno != EINTR);
if(ret < 0)
{
perror("read");
exit(1);
}
if(!ret) //对方以关闭
{
break;
}
printf("Receive data :%s\n",buf);
if(!strncasecmp(buf,"quit",4))
{
break;
}
}
close(newfd);
close(fd);
return 0;
}
client端简单示例代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
int fd = -1;
struct sockaddr_in sin;
int ret ;
/*1.创建socket套接子*/
if((fd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket");
exit(1);
}
/*2.链接服务器*/
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(5001);
sin.sin_addr.s_addr = inet_addr("192.168.1.3"); /*只适用于IPV4*/
/*inet_pton(AF_INET,"127.0.0.1",(void *)&sin.sin_addr.s_addr);*/
/*最后进行发送连接请求*/
if(connect(fd,(struct sockaddr*)&sin,sizeof(sin)) < 0)
{
perror("connect");
exit(1);
}
/*3.读写*/
ret = -1;
char buf[255];
while(1)
{
bzero(buf,255);
if(fgets(buf,256,stdin) == NULL)
{
continue;
}
ret = write(fd,buf,strlen(buf));
if(!strncasecmp(buf,"quit",4))
{
break;
}
}
/*关闭套接字*/
close(fd);
return 0;
}
存在的问题
在服务端read函数处是具有一个阻塞功能,当一个客户端连接服务器,服务器进行读的处理,程序一直阻塞在read处,如果又有一个客户端请求连接服务器,这时的服务器accept函数没有执行,所以不能连接心的客户端。这就意味着不能实现并发。改进方法,可以使用多线程,多进程编程等方法解决。