- ip地址:ipv4 ipv6唯一标识一台主机《高性能服务器编程》第三章和了解第2章ip报头
- mac地址:48位
- 端口:应用程序的代号。
- 虽然pid也能唯一标识一个进程,但是pid会变化,
- 无法并发的处理两个客户端。
- 两个人同时给一个人打电话,肯定有一个打不通(使用多线程或多进程就可以)
- //如果为空,accept就会阻塞住
socket 门迎
c = accept() 服务员
用多线程来处理并发
。主线程负责处理连接
子线程 接收数据
ser.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<pthread.h>
void *work_fun(void *arg)
{
int c = (int) arg;
while(1)
{
char buff[128] = {0};
int n = recv(c,buff,127,0);//recv 如果接受不到数据,会阻塞
if(n <= 0)
{
break;
}
printf("buff(%d) = %s\n",c,buff);
send(c,"ok",2,0);
}
printf("client close\n");
close(c);
}
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
exit(1);
}
//专用套接子地址结构
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
close(sockfd);
exit(1);
}
res = listen(sockfd,5);
if(res == -1)
{
close(sockfd);
exit(1);
}
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//只有被处理的已完成的连接才能接受数据
if(c < 0) //连接套接字
{
continue;
}
printf("accept :%d\n",c);
pthread_t id;
pthread_create(&id,NULL,work_fun,(void*)c);
}
}
cli.c
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1 )
{
exit(1);
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET; //家庭族 ipv4
saddr.sin_port = htons(6000); // 1024 4096保留端口
saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //ip地址
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
printf("connect falied\n");
close(sockfd);
exit(1);
}
while(1)
{
char buff[128] = {0};
printf("input\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)== 0)
{
break;
}
send(sockfd,buff,strlen(buff)-1,0);
memset(buff,0,128); //每次发送完数据,将缓冲区清空
recv(sockfd,buff,127,0);
printf("recv = %s\n",buff);
}
close(sockfd);
exit(0);
}
1、TCP :面向连接可靠的流式服务
比如你发快递,第一次给快递员,快递员拿走了,(已经在规定时间发货)(还没有发货,放在快递站仓库),你又要去再发一件,你的两件快递发出去的可能性就是,第一个已经发了,第二个后发和两个同时发出。当收件人的情况也是,力气大一点可以两个一块拿走,力气小就得分两次,或者件太大只能一次拿一个,件小可以一次收走。
同理对应recv
和send
。
send 是连续多次的将数据写到发送缓冲区中
recv和send 接收数据和发送数据的方式不是对应的(因为缓冲区)
如下图结果:
《高性能服务器编程》Tcp服务的特点第三章
int n = recv(c,buff,1,0);//recv 如果接受不到数据,会阻塞
ok还在发送缓冲区,后面的数据接受时
netstat -natp
打个比方比喻TCP,有个蓄水池,你可以里面倒水,蓄水池上有个龙头,你可以通过龙头将水池里的水放出来,然后用各种各样的容器装(杯子、矿泉水瓶、锅碗瓢盆)接水。
上面的例子中,往水池里倒几次水和接几次水是没有必然联系的,也就是说你可以只倒一次水,然后分10次接完。另外,水池里的水接多少就会少多少;往里面倒多少水,就会增加多少水,但是不能超过水池的容量,多出的水会溢出。
结合TCP的概念,水池就好比接收缓存,倒水就相当于发送数据,接水就相当于读取数据。好比你通过TCP连接给另一端发送数据,你只调用了一次write,发送了100个字节,但是对方可以分10次收完,每次10个字节;你也可以调用10次write,每次10个字节,但是对方可以一次就收完。(假设数据都能到达)但是,你发送的数据量不能大于对方的 (流量控制),如果你硬是要发送过量数据,则对方的缓存满了就会把多出的数据丢弃。
这种情况是设置非阻塞I/O模型,会把内存耗尽,因为socket是存在内核中的。
2、UDP :无连接的不可靠的数据包服务 (视频,图片,大文件)
UDP和TCP不同,发送端调用了几次write,接收端必须用相同次数的read读完。UPD是基于报文的,在接收的时候,每次最多只能读取一个报文,报文和报文是不会合并的,如果缓冲区小于报文长度,则多出的部分会被丢弃。也就说,如果不指定MSG_PEEK标志,每次读取操作将消耗一个报文。