server obiektowo c++ 11 thread

server obiektowo c++ 11 thread
BA
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 8 lat
  • Postów:15
0

Hej mam problem, chcę napisać server obiektowo w standardzie c++ 11. Jednak przeszkada jest wywolanie tworzenia wątku i funkcja która miałaby taki wątek obsłużyć..

Nagłówkowy klasy wygląda tak:

Kopiuj
 
/*
 * server.h
 *
 *  Created on: 10 sie 2015
 *      Author: michal
 */

#ifndef SERVER_H_
#define SERVER_H_
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>



class tcp_server
{
private:
	int sock;
	static int socket_thread;
	int port;
	struct sockaddr_in server_addr;
public:
    tcp_server();
	~tcp_server();
	void bind_(int port);
	void listen_();
	int accept_();
	void create_thread();
	void funkcja();
};


#endif /* SERVER_H_ */

A metody tak:

Kopiuj
#include "server.h"
#include "struktury.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <cstdio>
#include <string.h>  // memset()
#include <unistd.h>  // close()
#include <thread>


tcp_server::tcp_server()
{
	if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
	{
		perror("socket error ");
	}
	port = 8888;
}

tcp_server::~tcp_server()
{
	close(sock);
}

void tcp_server::bind_(int port)
{
	memset(&server_addr,0,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if((bind(sock,(struct sockaddr*)&server_addr,sizeof(struct sockaddr)))<0)
	{
		close(sock);
		perror("bing error");
	}
}

void tcp_server::listen_()
{
	if((listen(sock,5))<0)
	{
		perror("listen error");
	}
}

int tcp_server::accept_()
{
	int new_connect;
	fd_set rfds;
	struct timeval tv;
	int maxfd;
	FD_ZERO(&rfds);
	FD_SET(this->sock,&rfds);
	tv.tv_sec = 0;
	tv.tv_usec = 50000;
	maxfd = sock;

	if(select(maxfd+1,&rfds,NULL,NULL,&tv)>0)
	{
		socklen_t laddr_len = sizeof(sockaddr);
		if((new_connect = accept(sock, (struct sockaddr*)&server_addr,&laddr_len))<0)
		{
			perror("accept error");
		}
		this->socket_thread = new_connect;
		return new_connect;
	}
return 0;
}

void tcp_server::create_thread()
{
	std::thread t(&funkcja);
	t.detach();
}

void tcp_server::funkcja()
{
	char msg[512];
	int my_socket=socket_thread;
	fd_set rfds;
	struct timeval tv;
	int retval,maxfd;
	FD_ZERO(&rfds);
	FD_SET(my_socket,&rfds);
	tv.tv_sec=0;
	tv.tv_usec= 50000;
	maxfd=my_socket;
	int read_size,score;
//	----------------------------------
	 connect_req con_req;
	 connect_rsp con_rsp;
	 disconnect_req disc_req;
	 disconnect_rsp dis_con_rsp;
//	 ----------------------------------
	while(1)
	{
		puts("czelam na polaczenie");
		retval = select(maxfd+1,&rfds,NULL,NULL,&tv);
		if(retval>0)
     	{
			memset(msg,'\0',sizeof(msg));
	      	read_size = recv(my_socket,msg,512,0);
	      	if(read_size==0)
	      	{
	      		puts("client disconnected");
	      		fflush(stdout);
	      		close(my_socket);
	      		//~thread(); // destruktor watku
	      	}
	      	else if(read_size<0)
	      	{
	      		perror("recv failed*");
	      	}
	      	score = *((uint16_t *)&msg[ 0 ]);
	      	score = ntohs(score);
	      	switch(score)
	      	{
	        	case 1:
	      		{
	      		    con_req.deserializuj(msg);
	      			cout<<"Case0: "<<endl;
	      			cout<<"Code: "<<con_req.code<<endl;
	      			cout<<"Length: "<<con_req.length<<endl;
	      			cout<<"Message: "<<con_req.name;

	      			con_rsp.code = 0x0003;
	      			con_rsp.length = 0x0000;
	      			con_rsp.status = 144;
	      			char *q=con_rsp.serializuj();
	      			if(send(my_socket,q,sizeof(q),0)<0)
	      			{
	      			   puts("Send failed server_con_rsponse");
	      			   return;
	      			 }
	      		     delete [] q;
	      		   break;
	      		 }
	      		 case 3:
	      		 {
	      			break;
	      		 }
	      		 case 4:
	      		 {
	      		     disc_req.deserializuj(msg);
	      			 cout<<"Case4: "<<endl;
	      			 cout<<"Code: "<<disc_req.code<<endl;
	      			 cout<<"Lenhth: "<<disc_req.length<<endl;

	      			 dis_con_rsp.code = 0x0004;
	      			 dis_con_rsp.length = 0x0007;
	      			 dis_con_rsp.status = 97;
	      			 char* l=dis_con_rsp.serializuj();
	      			 if(send(my_socket,l,6,0)<0)
	      			 {
	      			     puts("Send failed");
	      			 }
	      			 cout<<"weszlo jak w maslo";
	      			 break;
	      		   }
	      		  case 5:
	      		  {
	      		      con_req.deserializuj(msg);
	      			  cout<<"Case5: "<<endl;
	      			  cout<<"Code: "<<con_req.code<<endl;
	      			  cout<<"Length: "<<con_req.length<<endl;
	      			  cout<<"Message: "<<con_req.name;

	      			  con_rsp.code = 0x0003;
	      			  con_rsp.length = 0x0000;
	      			  con_rsp.status = 144;
	      			  char *o=con_rsp.serializuj();
	      			  if(send(my_socket,o,sizeof(o),0)<0)
	      			  {
	      			      puts("Send failed server_con_rsponse");
	      				  return;
	      			  }
	      				delete [] o;
	      			  }
	      				break;
	      			default:
	      			{
	      				cout<<"brak dzialania";
	      			}
	      			break;
	      		}//switch

	      	}//retval>0
		tv.tv_sec = 0;
		tv.tv_usec=50000;
		FD_ZERO(&rfds);
		FD_SET(my_socket,&rfds);
     	}//while

	close(my_socket);
	return;
}//tcp funkcja
 

dołaczony jest jeszcze do tego plik struktury.h w którym zawarte sa metody serializacji i desarlizacji. Główny problem stanowi metoda void tcp_server::create_thread() z której to chcę wywołać metodę void tcp_server::funkcja(). Za wszelkie informacje i porady serdecznie dziękuję!

Kopiuj
 
stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad rok
  • Postów:607
0

Użyj std::bind, lamby albo funktora, albo jeszcze czegoś o czym nie wiem.
Tu przykład z bindem i lambdą https://ideone.com/ABF2IC

EDIT
na potrzeby tego programu uzyłem thread.join zamiast thread.detach

edytowany 1x, ostatnio: stryku
BA
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 8 lat
  • Postów:15
0

a mógłbys dokładniej opisac działanie funkcji bind? oraz powiedziec mi jak wywolac tą funkcje z argumentami przekazanymi do funkcji create_thread oraz funkcja?

stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad rok
  • Postów:607
0

Nie wytłumaczę tego lepiej niż jest tu http://en.cppreference.com/w/cpp/utility/functional/bind
Masz tam też przykłady z parametrami

BA
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 8 lat
  • Postów:15
0
Kopiuj
 
void tcp_server::create_thread(int my_socket)
{
//	auto f = std::bind(&tcp_server::funkcja,my_socket);
	auto f2 = [this](){funkcja(my_socket);};
	std::thread t(f2);
	t.detach();
}

void tcp_server::funkcja(int my_socket)

zmienilem funkcje teraz przyjmuja argumenty i dostaje blad..
error 'my_socket' is not captured

stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad rok
  • Postów:607
1
Kopiuj
auto f2 = [this, &my_socket](){funkcja(my_socket);};

(:

edytowany 1x, ostatnio: stryku
BA
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 8 lat
  • Postów:15
0

wszystko byloby dobrze ale my_socket przekazany do create_thred jest rowny 4, ale juz w wywolaniu funkcji: funkcja ma wartosc 0.

Kopiuj
	auto f2 = [this, &my_socket](){funkcja(my_socket);};
	cout<<"wywolanie my_scoket po thread create"<<my_socket<<endl;
	std::thread t(f2);
	t.detach();
 

tutaj niby coutem wyswietla dobrze przekazany deskryptor ale jak w funkcji funkcja(int my_socket) chce wypisać deskrypto
wyswietla mi 0.

edytowany 2x, ostatnio: banditdgl
stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad rok
  • Postów:607
1

Masz rację. Lipę odstawiłem. Takie coś będzie ok

Kopiuj
auto f2 = [this](int my_sock){funkcja(my_sock);};
std::thread t(f2, my_socket);
edytowany 1x, ostatnio: stryku
n0name_l
thread{&amp;klasa::funkcja, this, arg1, arg2}
BA
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 8 lat
  • Postów:15
0

a może ktos nakierucje mnie jeszcze jak zrobic zeby po sygnale ctl + c serwer wyslaw do wszyskich clientow informacje ze jest zamykany (czekal na informacje zwrotna) po czym zamykal wszystkie watki i konczyl dzialanie?? no i podobnie w clientcie :)
czy to obsluguje sie gdzies w select na 4 pozycji czy moze jakos trzeba zrobic sigaction??

PR
  • Rejestracja:około 11 lat
  • Ostatnio:4 miesiące
  • Lokalizacja:Pomorskie (Stare Kabaty)
1
Kopiuj
 
static void sigint_func(int signo) {
    puts("sigint\n");
    //wysylasz
}

int main(void) {
    if (signal(SIGINT, sigint_func) == SIG_ERR)
        return EXIT_FAILURE;
 }
edytowany 1x, ostatnio: Proxima
stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad rok
  • Postów:607
1
BA
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 8 lat
  • Postów:15
0

Wszystko ok ale chce przekazac ta informacje do kazdego z klientow ( czyt. do kazdego watku ) zeby rozeslal ramke konczaca poczekal na odpowiedz od wszyskich kliento a pozniej zakonczyl glowny watek :)

stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad rok
  • Postów:607
0

No to przekazujesz do std::signal tak napisaną metodę. W czym problem?

MO
Z funkcją w signal trzeba bardzo uważać. Jest szereg ograniczeń w ciele signal (choćby na bezpieczne i możliwe do użycia funkcje: http://man7.org/linux/man-pages/man7/signal.7.html ) oraz mniejsze przydzielone zasoby (choćby stos). Jeśli przypadek nie jest prosty lub reakcję na sygnał można odwlec, używam flagi.
MO
  • Rejestracja:około 10 lat
  • Ostatnio:około 22 godziny
  • Lokalizacja:Tam gdzie jest (centy)metro...
0

Może nie tyle "rozesłać informację" co ustawić flagę (bezpiecznie wielowątkowo) że jest przerwanie działania wątków i sprawdzać ją w bezpiecznym wielowątkowo momencie działania Twojej aplikacji. W C++ 11, implementacja nie posiada interrupt() które mogło by być naturalne w takim przypadku (boost ma).
Flagą może być także wspólna blokada jeśli sprawdzasz ją "nieblokująco" (try_lock()).


Każdy problem w informatyce można rozwiązać, dodając kolejny poziom pośredniości,z wyjątkiem problemu zbyt dużej liczby warstw pośredniości — David J. Wheeler
BA
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 8 lat
  • Postów:15
0

dodałem zmienna globalna info, gdy zmienna ta != 0 wtedy do whila w funkcji wykorzystywanej przez watek dodalem nastepujacego ifa

Kopiuj
 
		if(info!=0)
		{
			while(1)
			    {
				disc_req.code = 0x0004;
				disc_req.length = 0x0004;
				char *d = disc_req.serializuj();
			    if(send(my_socket,d,4,0)<0)
			    {
			        puts("Send failed");
			    }

			    delete [] d;
 

Jednakze nie wiem czy to eleganckie rozwiazanie// wydaje mi sie ze w tej funkcji otwieranej przez jest za duzo tresci i powinienem niektore rzeczy zmienic na metody co o tym myslicie? rozwazana funkcja:

Kopiuj
void tcp_server::funkcja(int my_socket)
{
	char msg[512];
	fd_set rfds;
	struct timeval tv;
	int retval,maxfd;
	FD_ZERO(&rfds);
	FD_SET(my_socket,&rfds);
	tv.tv_sec=0;
	tv.tv_usec= 50000;
	maxfd=my_socket;
	cout<<"my_socket "<<my_socket<<endl;
	cout<<"maxfd "<<maxfd<<endl;
	int read_size,score;
//	----------------------------------
	 connect_req con_req;
	 connect_rsp con_rsp;
	 disconnect_req disc_req;
	 disconnect_rsp dis_con_rsp,dis_resp;
//	 ----------------------------------
	while(1)
	{
		if(info!=0)
		{
			while(1)
			    {
				disc_req.code = 0x0004;
				disc_req.length = 0x0004;
				char *d = disc_req.serializuj();
			    if(send(my_socket,d,4,0)<0)
			    {
			        puts("Send failed");
			    }

			    delete [] d;

			    bzero(msg,512);
			    recv(my_socket,msg,512,0);
			    dis_resp.deserializuj(msg);
				cout<<"Code: "<<dis_resp.code<<endl;
				cout<<"Lenhth: "<<dis_resp.length<<endl;
				cout<<"Status: "<<dis_resp.status<<endl;
				if(dis_resp.status=='a')
			     break;
			    }
		}
		puts("czelam na polaczenie");
		std::cout<<"nasluch na deskryptorze: "<<my_socket<<endl;
		retval = select(maxfd+1,&rfds,NULL,NULL,&tv);
		if(retval>=0)
     	{
			memset(msg,'\0',sizeof(msg));
	      	read_size = recv(my_socket,msg,512,0);
	      	if(read_size==0)
	      	{
	      		puts("client disconnected");
	      		fflush(stdout);
	      		close(my_socket);
	      		//~thread(); // destruktor watku
	      	}
	      	else if(read_size<0)
	      	{
	      		perror("recv failed*");
	      	}
	      	score = *((uint16_t *)&msg[ 0 ]);
	      	score = ntohs(score);
	      	switch(score)
	      	{
	        	case 1:
	      		{
	      		    con_req.deserializuj(msg);
	      			cout<<"Case0: "<<endl;
	      			cout<<"Code: "<<con_req.code<<endl;
	      			cout<<"Length: "<<con_req.length<<endl;
	      			cout<<"Message: "<<con_req.name;

	      			con_rsp.code = 0x0003;
	      			con_rsp.length = 0x0000;
	      			con_rsp.status = 144;
	      			char *q=con_rsp.serializuj();
	      			if(send(my_socket,q,sizeof(q),0)<0)
	      			{
	      			   puts("Send failed server_con_rsponse");
	      			   return;
	      			 }
	      		     delete [] q;
	      		   break;
	      		 }
	      		 case 3:
	      		 {
	      			break;
	      		 }
	      		 case 4:
	      		 {
	      		     disc_req.deserializuj(msg);
	      			 cout<<"Case4: "<<endl;
	      			 cout<<"Code: "<<disc_req.code<<endl;
	      			 cout<<"Lenhth: "<<disc_req.length<<endl;

	      			 dis_con_rsp.code = 0x0004;
	      			 dis_con_rsp.length = 0x0007;
	      			 dis_con_rsp.status = 97;
	      			 char* l=dis_con_rsp.serializuj();
	      			 if(send(my_socket,l,6,0)<0)
	      			 {
	      			     puts("Send failed");
	      			 }
	      			 cout<<"weszlo jak w maslo";
	      			 break;
	      		   }
	      		  case 5:
	      		  {
	      		      con_req.deserializuj(msg);
	      			  cout<<"Case5: "<<endl;
	      			  cout<<"Code: "<<con_req.code<<endl;
	      			  cout<<"Length: "<<con_req.length<<endl;
	      			  cout<<"Message: "<<con_req.name;

	      			  con_rsp.code = 0x0003;
	      			  con_rsp.length = 0x0000;
	      			  con_rsp.status = 144;
	      			  char *o=con_rsp.serializuj();
	      			  if(send(my_socket,o,sizeof(o),0)<0)
	      			  {
	      			      puts("Send failed server_con_rsponse");
	      				  return;
	      			  }
	      				delete [] o;
	      			  }
	      				break;
	      			default:
	      			{
	      				cout<<"brak dzialania";
	      			}
	      			break;
	      		}//switch

	      	}//retval>0
		else if(retval==0)
		{

			cout<<"retval 0"<<endl;
		}
		else
		{
			break;
			cout<<"mniejsze od 0"<<endl;
		}
		tv.tv_sec = 3;
		tv.tv_usec=50000;
		FD_ZERO(&rfds);
		FD_SET(my_socket,&rfds);
     	}//while

	close(my_socket);
	return;
}//tcp funkcja



			    bzero(msg,512);
			    recv(my_socket,msg,512,0);
			    dis_resp.deserializuj(msg);
				cout<<"Code: "<<dis_resp.code<<endl;
				cout<<"Lenhth: "<<dis_resp.length<<endl;
				cout<<"Status: "<<dis_resp.status<<endl;
				if(dis_resp.status=='a')
			     break;
			    }
		}
edytowany 1x, ostatnio: banditdgl
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)